import { TSchema, Data } from '@evolution-sdk/evolution';
import { DEFAULT_SCHEMA_OPTIONS } from '../../types/evolution-schema-options';
import {
  AddressSchema,
  AssetClassSchema,
} from '@3rd-eye-labs/cardano-offchain-common';
import { option as O, function as F } from 'fp-ts';
import { IAssetPriceInfoSchema } from '../iasset/types';
import { MultisigSchema } from '../../types/multisig';
import { RationalSchema } from '../../types/rational';

export const ProtocolParamsSchema = TSchema.Struct({
  proposalDeposit: TSchema.Integer,
  votingPeriod: TSchema.Integer,
  effectiveDelay: TSchema.Integer,
  expirationPeriod: TSchema.Integer,
  proposingPeriod: TSchema.Integer,
  totalShards: TSchema.Integer,
  minimumQuorum: TSchema.Integer,
  maxTreasuryLovelaceSpend: TSchema.Integer,
  maxTreasuryIndySpend: TSchema.Integer,
  cdpRedemptionRequiredSignature: TSchema.NullOr(TSchema.ByteArray),
  electorate: TSchema.Integer,
  foundationMultisig: MultisigSchema,
});

export type ProtocolParams = typeof ProtocolParamsSchema.Type;

const UpgradePathSchema = TSchema.Struct({
  upgradeSymbol: TSchema.ByteArray,
});

const UpgradePathsSchema = TSchema.Struct({
  upgradeId: TSchema.Integer,
  /// Underlying representation of the following mapping: ValidatorHash -> UpgradePath
  upgradePaths: TSchema.Array(
    TSchema.Tuple([TSchema.ByteArray, UpgradePathSchema]),
  ),
});
export type UpgradePaths = typeof UpgradePathsSchema.Type;

const ProposeIAssetContentSchema = TSchema.Struct({
  asset: TSchema.ByteArray,
  debtMintingFeeRatio: RationalSchema,
  liquidationProcessingFeeRatio: RationalSchema,
  stabilityPoolWithdrawalFeeRatio: RationalSchema,
  redemptionReimbursementRatio: RationalSchema,
  redemptionProcessingFeeRatio: RationalSchema,
});
export type ProposeAssetContent = typeof ProposeIAssetContentSchema.Type;

const ModifyIAssetContentSchema = TSchema.Struct({
  asset: TSchema.ByteArray,
  newDebtMintingFeeRatio: RationalSchema,
  newLiquidationProcessingFeeRatio: RationalSchema,
  newStabilityPoolWithdrawalFeeRatio: RationalSchema,
  newRedemptionReimbursementRatio: RationalSchema,
  newRedemptionProcessingFeeRatio: RationalSchema,
});

const AddCollateralAssetContentSchema = TSchema.Struct({
  correspondingIAsset: TSchema.ByteArray,
  collateralAsset: AssetClassSchema,
  assetExtraDecimals: TSchema.Integer,
  assetPriceInfo: IAssetPriceInfoSchema,
  interestOracleNft: AssetClassSchema,
  redemptionRatio: RationalSchema,
  maintenanceRatio: RationalSchema,
  liquidationRatio: RationalSchema,
  minCollateralAmt: TSchema.Integer,
});

export type AddCollateralAsssetContent =
  typeof AddCollateralAssetContentSchema.Type;

const UpdateCollateralAssetContentSchema = TSchema.Struct({
  correspondingIAsset: TSchema.ByteArray,
  collateralAsset: AssetClassSchema,
  newAssetExtraDecimals: TSchema.Integer,
  newAssetPriceInfo: IAssetPriceInfoSchema,
  newInterestOracleNft: AssetClassSchema,
  newRedemptionRatio: RationalSchema,
  newMaintenanceRatio: RationalSchema,
  newLiquidationRatio: RationalSchema,
  newMinCollateralAmt: TSchema.Integer,
});

const ProposeStableswapPoolContentSchema = TSchema.Struct({
  iasset: TSchema.ByteArray,
  collateralAsset: AssetClassSchema,
  collateralToIassetRatio: RationalSchema,
  mintingFeeRatio: RationalSchema,
  redemptionFeeRatio: RationalSchema,
  feeManager: TSchema.NullOr(TSchema.ByteArray),
  minMintingAmount: TSchema.Integer,
  minRedemptionAmount: TSchema.Integer,
  stableswapValHash: TSchema.ByteArray,
});

export type ProposeStableswapPoolContent =
  typeof ProposeStableswapPoolContentSchema.Type;

const ModifyStableswapPoolContentSchema = TSchema.Struct({
  iasset: TSchema.ByteArray,
  collateralAsset: AssetClassSchema,
  newCollateralToIassetRatio: RationalSchema,
  newMintingFeeRatio: RationalSchema,
  newRedemptionFeeRatio: RationalSchema,
  newMintingEnabled: TSchema.Boolean,
  newRedemptionEnabled: TSchema.Boolean,
  newFeeManager: TSchema.NullOr(TSchema.ByteArray),
  newMinMintingAmount: TSchema.Integer,
  newMinRedemptionAmount: TSchema.Integer,
  newStableswapValHash: TSchema.ByteArray,
});

export type ModifyStableswapPoolContent =
  typeof ModifyStableswapPoolContentSchema.Type;

export const ProposalContentSchema = TSchema.Union(
  TSchema.Struct(
    {
      ProposeIAsset: ProposeIAssetContentSchema,
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      ModifyIAsset: ModifyIAssetContentSchema,
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      AddCollateralAsset: AddCollateralAssetContentSchema,
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      UpdateCollateralAsset: UpdateCollateralAssetContentSchema,
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      ProposeStableswapPool: ProposeStableswapPoolContentSchema,
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      ModifyStableswapPool: ModifyStableswapPoolContentSchema,
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      ModifyProtocolParams: TSchema.Struct(
        {
          newParams: ProtocolParamsSchema,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      UpgradeProtocol: TSchema.Struct(
        {
          content: UpgradePathsSchema,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      TextProposal: TSchema.ByteArray,
    },
    { flatInUnion: true },
  ),
);
export type ProposalContent = typeof ProposalContentSchema.Type;

const TreasuryWithdrawalItemSchema = TSchema.Struct({
  currencySymbol: TSchema.ByteArray,
  tokenName: TSchema.ByteArray,
  amount: TSchema.Integer,
});
export type TreasuryWithdrawalItem = typeof TreasuryWithdrawalItemSchema.Type;

export const TreasuryWithdrawalSchema = TSchema.Struct({
  destination: AddressSchema,
  value: TSchema.Array(TreasuryWithdrawalItemSchema),
});
export type TreasuryWithdrawal = typeof TreasuryWithdrawalSchema.Type;

const GovRedeemerSchema = TSchema.Union(
  TSchema.Struct(
    {
      CreatePoll: TSchema.Struct(
        {
          currentTime: TSchema.Integer,
          proposalOwner: TSchema.ByteArray,
          content: ProposalContentSchema,
          treasuryWithdrawal: TSchema.NullOr(TreasuryWithdrawalSchema),
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Struct(
    {
      WitnessEndPoll: TSchema.Struct(
        {
          currentTime: TSchema.Integer,
        },
        { flatFields: true },
      ),
    },
    { flatInUnion: true },
  ),
  TSchema.Literal('UpgradeGov', { flatInUnion: true }),
  TSchema.Literal('UpgradeVersion', { flatInUnion: true }),
);
export type GovRedeemer = typeof GovRedeemerSchema.Type;

const GovDatumSchema = TSchema.Struct({
  currentProposal: TSchema.Integer,
  protocolParams: ProtocolParamsSchema,
  currentVersion: TSchema.Integer,
  iassetsCount: TSchema.Integer,
  activeProposals: TSchema.Integer,
});
export type GovDatum = typeof GovDatumSchema.Type;

export function serialiseGovRedeemer(r: GovRedeemer): string {
  return Data.withSchema(GovRedeemerSchema).toCBORHex(r);
}

export function serialiseGovDatum(d: GovDatum): string {
  return Data.withSchema(GovDatumSchema).toCBORHex(d);
}

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

export function parseGovDatumOrThrow(datum: string): GovDatum {
  return F.pipe(
    parseGovDatum(datum),
    O.match(() => {
      throw new Error('Expected a Gov datum.');
    }, F.identity),
  );
}
