import { TSchema, Data } from '@evolution-sdk/evolution';
import {
  ProposalContentSchema,
  TreasuryWithdrawalSchema,
} from '../gov/types-new';
import {
  AddressSchema,
  OutputReferenceSchema,
} from '@3rd-eye-labs/cardano-offchain-common';
import { option as O, function as F } from 'fp-ts';
import { match, P } from 'ts-pattern';
import { DEFAULT_SCHEMA_OPTIONS } from '../../types/evolution-schema-options';

const PollStatusSchema = TSchema.Struct({
  yesVotes: TSchema.Integer,
  noVotes: TSchema.Integer,
});

export type PollStatus = typeof PollStatusSchema.Type;

const PollManagerContentSchema = TSchema.Struct({
  pollId: TSchema.Integer,
  pollOwner: TSchema.ByteArray,
  content: ProposalContentSchema,
  treasuryWithdrawal: TSchema.NullOr(TreasuryWithdrawalSchema),
  status: PollStatusSchema,
  votingEndTime: TSchema.Integer,
  createdShardsCount: TSchema.Integer,
  talliedShardsCount: TSchema.Integer,
  totalShardsCount: TSchema.Integer,
  proposingEndTime: TSchema.Integer,
  expirationTime: TSchema.Integer,
  protocolVersion: TSchema.Integer,
  minimumQuorum: TSchema.Integer,
});

export type PollManagerContent = typeof PollManagerContentSchema.Type;

const PollShardContentSchema = TSchema.Struct({
  pollId: TSchema.Integer,
  status: PollStatusSchema,
  votingEndTime: TSchema.Integer,
  managerAddress: AddressSchema,
});

export type PollShardContent = typeof PollShardContentSchema.Type;

const PollDatumSchema = TSchema.Union(
  TSchema.Struct(
    {
      PollManager: PollManagerContentSchema,
    },
    { flatInUnion: true },
  ),
  TSchema.Struct({ PollShard: PollShardContentSchema }, { flatInUnion: true }),
);

export type PollDatum = typeof PollDatumSchema.Type;

const VoteOptionSchema = TSchema.Union(
  TSchema.Literal('Yes', { flatInUnion: true }),
  TSchema.Literal('No', { flatInUnion: true }),
);
export type VoteOption = typeof VoteOptionSchema.Type;

const PollShardRedeemerSchema = TSchema.Union(
  TSchema.Struct({ Vote: VoteOptionSchema }, { flatInUnion: true }),
  TSchema.Struct(
    {
      MergeShards: TSchema.Struct(
        {
          currentTime: TSchema.Integer,
          pollManagerRef: OutputReferenceSchema,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
);
export type PollShardRedeemer = typeof PollShardRedeemerSchema.Type;

const PollManagerRedeemerSchema = TSchema.Union(
  TSchema.Struct(
    {
      EndPoll: TSchema.Struct(
        { currentTime: TSchema.Integer },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      CreateShards: TSchema.Struct(
        { currentTime: TSchema.Integer },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      MergeShardsManager: TSchema.Struct(
        { currentTime: TSchema.Integer },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
);
export type PollManagerRedeemer = typeof PollManagerRedeemerSchema.Type;

export function serialisePollManagerRedeemer(r: PollManagerRedeemer): string {
  return Data.withSchema(PollManagerRedeemerSchema).toCBORHex(r);
}

export function serialisePollShardRedeemer(r: PollShardRedeemer): string {
  return Data.withSchema(PollShardRedeemerSchema).toCBORHex(r);
}

export function parsePollShardRedeemer(
  datum: string,
): O.Option<PollShardRedeemer> {
  try {
    return O.some(
      Data.withSchema(
        PollShardRedeemerSchema,
        DEFAULT_SCHEMA_OPTIONS,
      ).fromCBORHex(datum),
    );
  } catch (_) {
    return O.none;
  }
}

export function parsePollShardRedeemerOrThrow(
  datum: string,
): PollShardRedeemer {
  return F.pipe(
    parsePollShardRedeemer(datum),
    O.match(() => {
      throw new Error('Expected a poll shard redeemer.');
    }, F.identity),
  );
}

export function parsePollManager(datum: string): O.Option<PollManagerContent> {
  try {
    return match(
      Data.withSchema(PollDatumSchema, DEFAULT_SCHEMA_OPTIONS).fromCBORHex(
        datum,
      ),
    )
      .with({ PollManager: P.select() }, (res) => O.some(res))
      .otherwise(() => O.none);
  } catch (_) {
    return O.none;
  }
}

export function parsePollManagerOrThrow(datum: string): PollManagerContent {
  return F.pipe(
    parsePollManager(datum),
    O.match(() => {
      throw new Error('Expected a poll manager datum.');
    }, F.identity),
  );
}

export function parsePollShard(datum: string): O.Option<PollShardContent> {
  try {
    return match(
      Data.withSchema(PollDatumSchema, DEFAULT_SCHEMA_OPTIONS).fromCBORHex(
        datum,
      ),
    )
      .with({ PollShard: P.select() }, (res) => O.some(res))
      .otherwise(() => O.none);
  } catch (_) {
    return O.none;
  }
}

export function parsePollShardOrThrow(datum: string): PollShardContent {
  return F.pipe(
    parsePollShard(datum),
    O.match(() => {
      throw new Error('Expected a poll shard datum.');
    }, F.identity),
  );
}

export function serialisePollDatum(d: PollDatum): string {
  return Data.withSchema(PollDatumSchema).toCBORHex(d);
}
