import { TSchema, Data } from '@evolution-sdk/evolution';
import { DEFAULT_SCHEMA_OPTIONS } from '../../types/evolution-schema-options';
import {
  AssetClassSchema,
  OutputReferenceSchema,
} from '@3rd-eye-labs/cardano-offchain-common';
import { option as O, function as F } from 'fp-ts';
import { UTxO } from '@lucid-evolution/lucid';
import { RationalSchema } from '../../types/rational';
import { OracleIdxSchema } from '../price-oracle/types-new';

const RobOrderTypeSchema = TSchema.Union(
  TSchema.Struct(
    {
      BuyIAssetOrder: TSchema.Struct(
        {
          collateralAsset: AssetClassSchema,
          maxPrice: RationalSchema,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      SellIAssetOrder: TSchema.Struct(
        {
          allowedCollateralAssets: TSchema.Array(
            TSchema.Tuple([AssetClassSchema, RationalSchema]),
          ),
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
);

export type RobOrderType = typeof RobOrderTypeSchema.Type;

const RobDatumSchema = TSchema.Struct({
  owner: TSchema.ByteArray,
  iasset: TSchema.ByteArray,
  orderType: RobOrderTypeSchema,
  robRefInput: OutputReferenceSchema,
});

export type RobDatum = typeof RobDatumSchema.Type;

const RobRedeemerSchema = TSchema.Union(
  TSchema.Struct(
    {
      Redeem: TSchema.Struct(
        {
          ownInputIdx: TSchema.Integer,
          collateralAssetRefInputIdx: TSchema.Integer,
          iassetRefInputIdx: TSchema.Integer,
          continuingOutputIdx: TSchema.Integer,
          sellOrderAllowedAssetsIdx: TSchema.Integer,
          priceOracleIdx: OracleIdxSchema,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Literal('Cancel', { flatInUnion: true }),
  TSchema.Literal('UpgradeVersion', { flatInUnion: true }),
);

export type RobRedeemer = typeof RobRedeemerSchema.Type;

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

export function parseRobDatumOrThrow(datum: string): RobDatum {
  return F.pipe(
    parseRobDatum(datum),
    O.match(() => {
      throw new Error('Expected an ROB datum.');
    }, F.identity),
  );
}

export function serialiseRobDatum(d: RobDatum): string {
  return Data.withSchema(RobDatumSchema, DEFAULT_SCHEMA_OPTIONS).toCBORHex(d);
}

export function serialiseRobRedeemer(r: RobRedeemer): string {
  return Data.withSchema(RobRedeemerSchema, DEFAULT_SCHEMA_OPTIONS).toCBORHex(
    r,
  );
}

export function parseRobRedeemer(
  redeemerCborHex: string,
): O.Option<RobRedeemer> {
  try {
    return O.some(
      Data.withSchema(RobRedeemerSchema, DEFAULT_SCHEMA_OPTIONS).fromCBORHex(
        redeemerCborHex,
      ),
    );
  } catch (_) {
    return O.none;
  }
}

export function parseRobRedeemerOrThrow(redeemerCborHex: string): RobRedeemer {
  return F.pipe(
    parseRobRedeemer(redeemerCborHex),
    O.match(() => {
      throw new Error('Expected an ROB redeemer.');
    }, F.identity),
  );
}

export type RobOutput = { datum: RobDatum; utxo: UTxO };
