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,
  OutputReferenceSchema,
} from '@3rd-eye-labs/cardano-offchain-common';
import { match, P } from 'ts-pattern';
import { RationalSchema } from '../../types/rational';
import { OracleIdxSchema } from '../price-oracle/types-new';

// const CdpParamsSchema = Core.TSchema.Struct({
//   cdp_auth_token: EvoCommon.AssetClassSchema,
//   cdp_asset_symbol: Core.TSchema.ByteArray,
//   iasset_auth_token: EvoCommon.AssetClassSchema,
//   stability_pool_auth_token: EvoCommon.AssetClassSchema,
//   version_record_token: EvoCommon.AssetClassSchema,
//   upgrade_token: EvoCommon.AssetClassSchema,
//   collector_val_hash: Core.TSchema.ByteArray,
//   sp_val_hash: Core.TSchema.ByteArray,
//   gov_nft: EvoCommon.AssetClassSchema,
//   min_collateral_in_lovelace: Core.TSchema.Integer,
//   partial_redemption_extra_fee_lovelace: Core.TSchema.Integer,
//   bias_time: Core.TSchema.Integer,
//   treasury_val_hash: Core.TSchema.ByteArray,
// });

// export type CdpParams = typeof CdpParamsSchema.Type;

const CdpRedeemerSchema = TSchema.Union(
  TSchema.Struct(
    {
      AdjustCdp: TSchema.Struct(
        {
          currentTime: TSchema.Integer,
          debtAdjustment: TSchema.Integer,
          collateralAdjustment: TSchema.Integer,
          priceOracleIdx: OracleIdxSchema,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      CloseCdp: TSchema.Struct(
        {
          currentTime: TSchema.Integer,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Literal('RedeemCdp', { flatInUnion: true }),
  TSchema.Struct(
    {
      FreezeCdp: TSchema.Struct(
        {
          currentTime: TSchema.Integer,
          priceOracleIdx: OracleIdxSchema,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Literal('MergeCdps', { flatInUnion: true }),
  TSchema.Struct(
    {
      MergeAuxiliary: TSchema.Struct(
        {
          mainMergeUtxo: OutputReferenceSchema,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Literal('Liquidate', { flatInUnion: true }),
  TSchema.Struct(
    {
      SettleInterest: TSchema.Struct(
        {
          interestCollectorInputIndex: TSchema.Integer,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      Stableswap: TSchema.Struct(
        {
          forwardingInputIndex: TSchema.Integer,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Literal('UpgradeVersion', { flatInUnion: true }),
);

export type CdpRedeemer = typeof CdpRedeemerSchema.Type;

const RedeemCdpWithdrawalRedeemerSchema = TSchema.Struct({
  cdpOutReference: OutputReferenceSchema,
  currentTime: TSchema.Integer,
  priceOracleIdx: OracleIdxSchema,
});

export type RedeemCdpWithdrawalRedeemer =
  typeof RedeemCdpWithdrawalRedeemerSchema.Type;

const CDPFeesSchema = TSchema.Union(
  TSchema.Struct(
    {
      ActiveCDPInterestTracking: TSchema.Struct(
        {
          lastSettled: TSchema.Integer,
          unitaryInterestSnapshot: TSchema.Integer,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      FrozenCDPAccumulatedFees: TSchema.Struct(
        {
          iassetInterest: TSchema.Integer,
          collateralTreasury: TSchema.Integer,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
);

const CDPContentSchema = TSchema.Struct({
  cdpOwner: TSchema.NullOr(TSchema.ByteArray),
  iasset: TSchema.ByteArray,
  collateralAsset: AssetClassSchema,
  mintedAmt: TSchema.Integer,
  cdpFees: CDPFeesSchema,
});

export type CDPContent = typeof CDPContentSchema.Type;

const StableswapPoolContentSchema = TSchema.Struct({
  /** Use the HEX encoding */
  iasset: TSchema.ByteArray,
  collateralAsset: AssetClassSchema,
  collateralToIassetRatio: RationalSchema,
  mintingFeeRatio: RationalSchema,
  redemptionFeeRatio: RationalSchema,
  mintingEnabled: TSchema.Boolean,
  redemptionEnabled: TSchema.Boolean,
  feeManager: TSchema.NullOr(TSchema.ByteArray),
  minMintOrderAmount: TSchema.Integer,
  minRedemptionOrderAmount: TSchema.Integer,
  stableswapValHash: TSchema.ByteArray,
});

export type StableswapPoolContent = typeof StableswapPoolContentSchema.Type;

const CDPDatumSchema = TSchema.Union(
  TSchema.Struct({ CDP: CDPContentSchema }, { flatInUnion: true }),
  TSchema.Struct(
    { StableswapPool: StableswapPoolContentSchema },
    { flatInUnion: true },
  ),
);
export type CDPDatum = typeof CDPDatumSchema.Type;

export function serialiseCdpRedeemer(r: CdpRedeemer): string {
  return Data.withSchema(CdpRedeemerSchema, DEFAULT_SCHEMA_OPTIONS).toCBORHex(
    r,
  );
}

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

export function parseCdpRedeemerOrThrow(datum: string): CdpRedeemer {
  return F.pipe(
    parseCdpRedeemer(datum),
    O.match(() => {
      throw new Error('Expected a cdp redeemer.');
    }, F.identity),
  );
}

export function serialiseRedeemCdpWithdrawalRedeemer(
  r: RedeemCdpWithdrawalRedeemer,
): string {
  return Data.withSchema(
    RedeemCdpWithdrawalRedeemerSchema,
    DEFAULT_SCHEMA_OPTIONS,
  ).toCBORHex(r);
}

export function serialiseCdpDatum(d: CDPContent): string {
  return Data.withSchema(CDPDatumSchema, DEFAULT_SCHEMA_OPTIONS).toCBORHex({
    CDP: d,
  });
}

export function serialiseStableswapPoolDatum(d: StableswapPoolContent): string {
  return Data.withSchema(CDPDatumSchema, DEFAULT_SCHEMA_OPTIONS).toCBORHex({
    StableswapPool: d,
  });
}

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

export function parseCdpDatumOrThrow(datum: string): CDPContent {
  return F.pipe(
    parseCdpDatum(datum),
    O.match(() => {
      throw new Error('Expected a CDP datum.');
    }, F.identity),
  );
}

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

export function parseStableswapPoolDatumOrThrow(
  datum: string,
): StableswapPoolContent {
  return F.pipe(
    parseStableswapPoolDatum(datum),
    O.match(() => {
      throw new Error('Expected a stableswap pool datum.');
    }, F.identity),
  );
}
