import { TSchema, Data } from '@evolution-sdk/evolution';
import { DEFAULT_SCHEMA_OPTIONS } from '../../types/evolution-schema-options';
import { option as O, function as F } from 'fp-ts';
import { AssetClassSchema } from '@3rd-eye-labs/cardano-offchain-common';
import { match, P } from 'ts-pattern';
import { ParsedOutput } from '../../types/generic';
import { RationalSchema } from '../../types/rational';

export const IAssetPriceInfoSchema = TSchema.Union(
  TSchema.Struct(
    {
      Delisted: TSchema.Struct({ price: RationalSchema }, { flatFields: true }),
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      OracleNft: AssetClassSchema,
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      DeferredValidation: TSchema.Struct(
        { feedValHash: TSchema.ByteArray },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
);

export type IAssetPriceInfo = typeof IAssetPriceInfoSchema.Type;

const IAssetContentSchema = TSchema.Struct({
  assetName: TSchema.ByteArray,
  collateralAssetsCount: TSchema.Integer,
  debtMintingFeeRatio: RationalSchema,
  liquidationProcessingFeeRatio: RationalSchema,
  stabilityPoolWithdrawalFeeRatio: RationalSchema,
  redemptionReimbursementRatio: RationalSchema,
  redemptionProcessingFeeRatio: RationalSchema,
  firstIAsset: TSchema.Boolean,
  nextIAsset: TSchema.NullOr(TSchema.ByteArray),
});

export type IAssetContent = typeof IAssetContentSchema.Type;

const CollateralAssetContentSchema = TSchema.Struct({
  iasset: TSchema.ByteArray,
  collateralAsset: AssetClassSchema,
  extraDecimals: TSchema.Integer,
  priceInfo: IAssetPriceInfoSchema,
  interestOracleNft: AssetClassSchema,
  redemptionRatio: RationalSchema,
  maintenanceRatio: RationalSchema,
  liquidationRatio: RationalSchema,
  minCollateralAmt: TSchema.Integer,
  firstCollateralAsset: TSchema.Boolean,
  nextCollateralAsset: TSchema.NullOr(AssetClassSchema),
});

export type CollateralAssetContent = typeof CollateralAssetContentSchema.Type;

const IAssetDatumSchema = TSchema.Union(
  TSchema.Struct({ IAssetContent: IAssetContentSchema }, { flatInUnion: true }),
  TSchema.Struct(
    { CollateralAssetContent: CollateralAssetContentSchema },
    { flatInUnion: true },
  ),
);

export type IAssetDatum = typeof IAssetDatumSchema.Type;

const IAssetRedeemerSchema = TSchema.Union(
  TSchema.Struct(
    {
      UpdateOrInsertAsset: TSchema.Struct(
        { executeInputIdx: TSchema.Integer },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Literal('UpgradeVersion', { flatInUnion: true }),
);

export type IAssetRedeemer = typeof IAssetRedeemerSchema.Type;

export function serialiseIAssetRedeemer(r: IAssetRedeemer): string {
  return Data.withSchema(
    IAssetRedeemerSchema,
    DEFAULT_SCHEMA_OPTIONS,
  ).toCBORHex(r);
}

export function serialiseIAssetDatum(d: IAssetDatum): string {
  return Data.withSchema(IAssetDatumSchema).toCBORHex(d);
}

export function parseIAssetDatum(datum: string): O.Option<IAssetContent> {
  try {
    return match(
      Data.withSchema(IAssetDatumSchema, DEFAULT_SCHEMA_OPTIONS).fromCBORHex(
        datum,
      ),
    )
      .with({ IAssetContent: P.select() }, (content) => O.some(content))
      .otherwise(() => O.none);
  } catch (_) {
    return O.none;
  }
}

export function parseIAssetDatumOrThrow(datum: string): IAssetContent {
  return F.pipe(
    parseIAssetDatum(datum),
    O.match(() => {
      throw new Error('Expected a Iasset datum.');
    }, F.identity),
  );
}

export function parseCollateralAssetDatum(
  datum: string,
): O.Option<CollateralAssetContent> {
  try {
    return match(
      Data.withSchema(IAssetDatumSchema, DEFAULT_SCHEMA_OPTIONS).fromCBORHex(
        datum,
      ),
    )
      .with({ CollateralAssetContent: P.select() }, (content) =>
        O.some(content),
      )
      .otherwise(() => O.none);
  } catch (_) {
    return O.none;
  }
}

export function parseCollateralAssetDatumOrThrow(
  datum: string,
): CollateralAssetContent {
  return F.pipe(
    parseCollateralAssetDatum(datum),
    O.match(() => {
      throw new Error('Expected a collateral asset datum.');
    }, F.identity),
  );
}

export type IAssetOutput = ParsedOutput<IAssetContent>;
export type CollateralAssetOutput = ParsedOutput<CollateralAssetContent>;
