import { AssetClass } from '@3rd-eye-labs/cardano-offchain-common';
import {
  Credential,
  fromHex,
  fromText,
  OutRef,
  toHex,
  toText,
} from '@lucid-evolution/lucid';
import { AssetClassD, StakeCredential } from './generic';
import {
  DerivedPythPrice,
  PythFeedParams,
  toDataDerivedPythPrice,
} from '../contracts/pyth-feed/types';
import { match } from 'ts-pattern';
import { P } from 'ts-pattern';

export interface CurrencySymbolSP {
  unCurrencySymbol: string;
}

export interface TokenNameSP {
  unTokenName: string;
}

/**
 * AssetClassSP used in System Params
 */
export type AssetClassSP = [CurrencySymbolSP, TokenNameSP];

export type PythPriceConfigurationSP = {
  priceFeedId: number;
};

export type DerivedPythPriceSP =
  | {
      tag: 'value';
      val: PythPriceConfigurationSP;
    }
  | {
      tag: 'inverse';
      val: DerivedPythPriceSP;
    }
  | {
      tag: 'divide';
      val: [DerivedPythPriceSP, DerivedPythPriceSP];
    };

export type PythFeedParamsSP = {
  config: DerivedPythPriceSP;
  pythStatePolicyId: CurrencySymbolSP;
};

export function fromSysParamsDerivedPythPrice(
  cfg: DerivedPythPriceSP,
): DerivedPythPrice {
  return match(cfg)
    .returnType<DerivedPythPrice>()
    .with({ tag: 'value', val: P.select() }, (val) => {
      return {
        Value: {
          configuration: {
            priceFeedId: BigInt(val.priceFeedId),
          },
        },
      };
    })
    .with({ tag: 'inverse', val: P.select() }, (val) => {
      return {
        Inverse: {
          value: toDataDerivedPythPrice(fromSysParamsDerivedPythPrice(val)),
        },
      };
    })
    .with({ tag: 'divide', val: P.select() }, (val) => {
      return {
        Divide: {
          x: toDataDerivedPythPrice(fromSysParamsDerivedPythPrice(val[0])),
          y: toDataDerivedPythPrice(fromSysParamsDerivedPythPrice(val[1])),
        },
      };
    })
    .exhaustive();
}

export function fromSysParamsPythFeedParams(
  params: PythFeedParamsSP,
): PythFeedParams {
  return {
    config: fromSysParamsDerivedPythPrice(params.config),
    pythStatePolicyId: fromHex(params.pythStatePolicyId.unCurrencySymbol),
  };
}

export type PythFeedConfig = {
  params: PythFeedParamsSP;
  pythFeedValHash: string;
  pythFeedValScriptRef: ScriptReference;
};

export type PythConfig = {
  pythStateAssetClass: AssetClassSP;
  /**
   * Key is in the format: "iAsset.ascii/Collateral.currency.hex.Collateral.tokenName.hex".
   * Use the `getPythFeedConfig` helper to querying.
   */
  pythFeeds: {
    [key: string]: PythFeedConfig;
  };
};

export function getPythFeedConfig(
  cfg: PythConfig,
  iasset: Uint8Array<ArrayBufferLike>,
  collateralAsset: AssetClass,
): PythFeedConfig {
  const res =
    cfg.pythFeeds[
      `${toText(toHex(iasset))}/${toHex(collateralAsset.currencySymbol)}.${toHex(collateralAsset.tokenName)}`
    ];

  if (res == null)
    throw new Error('No Pyth config known for such assets combination');

  return res;
}

export interface SystemParams {
  versionRecordParams: VersionRecordParams;
  validatorHashes: ValidatorHashes;
  treasuryParams: TreasuryParamsSP;
  startTime: StartTime;
  stakingParams: StakingParamsSP;
  stabilityPoolParams: StabilityPoolParamsSP;
  stableswapParams: StableswapParamsSP;
  scriptReferences: ScriptReferences;
  pollShardParams: PollShardParamsSP;
  pollManagerParams: PollManagerParamsSP;
  robParams: RobParamsSP;
  interestCollectionParams: InterestCollectionParamsSP;
  indyToken: AssetClassSP;
  govParams: GovParamsSP;
  executeParams: ExecuteParamsSP;
  collectorParams: CollectorParamsSP;
  cdpParams: CdpParamsSP;
  cdpCreatorParams: CDPCreatorParamsSP;
  cdpRedeemParams: CdpRedeemParamsSP;
  iassetParams: IAssetParamsSP;
  pythConfig: PythConfig;
}
export type ValidatorHashes = {
  versionRegistryHash: string;
  treasuryHash: string;
  stableswapHash: string;
  stakingHash: string;
  stabilityPoolHash: string;
  pollShardHash: string;
  pollManagerHash: string;
  robHash: string;
  interestCollectionHash: string;
  govHash: string;
  executeHash: string;
  collectorHash: string;
  cdpHash: string;
  cdpCreatorHash: string;
  iassetHash: string;
};
export interface AddressCredential {
  tag: string;
  contents: PubKeyHash;
}

export interface ScriptCredential {
  tag: string;
  contents: {
    tag: string;
    contents: string;
  };
}
export interface PubKeyHash {
  getPubKeyHash: string;
}
export interface VersionRecordParams {
  upgradeToken: AssetClassSP;
}
export interface TreasuryParamsSP {
  upgradeToken: AssetClassSP;
  versionRecordToken: AssetClassSP;
  treasuryUtxosStakeCredential: ScriptCredential | undefined;
}
export interface StartTime {
  slot: number;
  blockHeader: string;
}
export interface StakingParamsSP {
  versionRecordToken: AssetClassSP;
  stakingToken: AssetClassSP;
  stakingManagerNFT: AssetClassSP;
  pollToken: AssetClassSP;
  indyToken: AssetClassSP;
  collectorValHash: string;
}
export interface StabilityPoolParamsSP {
  versionRecordToken: AssetClassSP;
  stabilityPoolToken: AssetClassSP;
  snapshotEpochToScaleToSumToken: AssetClassSP;
  iAssetAuthToken: AssetClassSP;
  iassetValHash: string;
  cdpToken: AssetClassSP;
  assetSymbol: CurrencySymbolSP;
  accountToken: AssetClassSP;
  accountCreateFeeLovelaces: number;
  accountProcessingCooldownMs: number;
  accountProcessingBiasMs: number;
  stakeCredential: ScriptCredential | undefined;
}

export interface StableswapParamsSP {
  iassetSymbol: CurrencySymbolSP;
  cdpToken: AssetClassSP;
  versionRecordToken: AssetClassSP;
  cdpValHash: string;
  treasuryValHash: string;
}

export interface ScriptReferences {
  versionRegistryValidatorRef: ScriptReference;
  treasuryValidatorRef: ScriptReference;
  stakingValidatorRef: ScriptReference;
  stabilityPoolValidatorRef: ScriptReference;
  stableswapValidatorRef: ScriptReference;
  pollShardValidatorRef: ScriptReference;
  pollManagerValidatorRef: ScriptReference;
  robValidatorRef: ScriptReference;
  interestCollectionValidatorRef: ScriptReference;
  iAssetTokenPolicyRef: ScriptReference;
  governanceValidatorRef: ScriptReference;
  executeValidatorRef: ScriptReference;
  collectorValidatorRef: ScriptReference;
  cdpValidatorRef: ScriptReference;
  cdpCreatorValidatorRef: ScriptReference;
  cdpRedeemValidatorRef: ScriptReference;
  iassetValidatorRef: ScriptReference;
  authTokenPolicies: AuthTokenPolicies;
}
export interface Output {
  scriptRef: ScriptRef;
  output: ScriptOutput;
}
export interface ScriptRef {
  tag: string;
  contents?: string[] | null;
}
export interface ScriptOutput {
  referenceScript: string;
  datum: AddressCredentialOrDatum;
  amount: Amount;
  address: AddressSP;
}
export interface AddressCredentialOrDatum {
  tag: string;
  contents: string;
}
export interface Amount {
  getValue?:
    | ((CurrencySymbolSP | (number[] | null)[] | null)[] | null)[]
    | null;
}
export interface AddressSP {
  addressStakingCredential?: null;
  addressCredential: AddressCredentialOrDatum;
}
export interface Input {
  transactionId: string;
  index: number;
}

export interface ScriptReference {
  output?: Output;
  input: Input;
}

export interface AuthTokenPolicies {
  upgradeTokenRef: ScriptReference;
  stakingTokenRef: ScriptReference;
  stabilityPoolTokenRef: ScriptReference;
  stabilityPoolAuthTokenRef: ScriptReference;
  snapshotEpochToScaleToSumTokenRef: ScriptReference;
  pollManagerTokenRef: ScriptReference;
  iAssetAuthTokenRef: ScriptReference;
  iAssetTokenRef: ScriptReference;
  collateralAssetTokenRef: ScriptReference;
  cdpAuthTokenRef: ScriptReference;
  accountTokenRef: ScriptReference;
  versionRecordTokenPolicyRef: ScriptReference;
}

export interface RobParamsSP {
  versionRecordToken: AssetClassSP;
  iassetAuthToken: AssetClassSP;
  collateralAssetAuthToken: AssetClassSP;
  iassetValHash: string;
  iassetPolicyId: CurrencySymbolSP;
}

export interface InterestCollectionParamsSP {
  versionRecordNft: AssetClassSP;
  multisigUtxoNft: AssetClassSP;
  cdpAuthTk: AssetClassSP;
  collateralAssetAuthTk: AssetClassSP;
  govAuthTk: AssetClassSP;
  cdpAssetSymbol: CurrencySymbolSP;
  cdpBiasTime: bigint;
  interestSettlementCooldown: bigint;
}

export interface PollShardParamsSP {
  stakingValHash: string;
  stakingToken: AssetClassSP;
  pollToken: AssetClassSP;
  indyAsset: AssetClassSP;
}

export interface PollManagerParamsSP {
  upgradeToken: AssetClassSP;
  treasuryValHash: string;
  shardsValHash: string;
  pollToken: AssetClassSP;
  pBiasTime: bigint;
  indyAsset: AssetClassSP;
  govNFT: AssetClassSP;
  govExecuteValHash: string;
}

export interface GovParamsSP {
  versionRecordToken: AssetClassSP;
  upgradeToken: AssetClassSP;
  pollToken: AssetClassSP;
  pollManagerValHash: string;
  iassetValHash: string;
  indyAsset: AssetClassSP;
  iAssetAuthToken: AssetClassSP;
  govNFT: AssetClassSP;
  gBiasTime: bigint;
  daoIdentityToken: AssetClassSP;
  iassetSymbol: CurrencySymbolSP;
}

export interface ExecuteParamsSP {
  govNFT: AssetClassSP;
  upgradeToken: AssetClassSP;
  iAssetToken: AssetClassSP;
  collateralAssetToken: AssetClassSP;
  stabilityPoolToken: AssetClassSP;
  cdpCreatorToken: AssetClassSP;
  cdpToken: AssetClassSP;
  versionRecordToken: AssetClassSP;
  iassetValHash: string;
  cdpValHash: string;
  sPoolValHash: string;
  versionRegistryValHash: string;
  treasuryValHash: string;
}
export interface CollectorParamsSP {
  versionRecordToken: AssetClassSP;
  stakingToken: AssetClassSP;
  stakingManagerNFT: AssetClassSP;
}
export interface CdpRedeemParamsSP {
  treasuryValHash: string;
  iassetValHash: string;
  partialRedemptionExtraFeeLovelace: number;
  iAssetAuthToken: AssetClassSP;
  collateralAssetAuthToken: AssetClassSP;
  govNFT: AssetClassSP;
  interestCollectorValHash: string;
  cdpAuthToken: AssetClassSP;
  cdpAssetSymbol: CurrencySymbolSP;
  biasTime: bigint;
}
export interface CdpParamsSP {
  upgradeToken: AssetClassSP;
  versionRecordToken: AssetClassSP;
  treasuryValHash: string;
  stabilityPoolAuthToken: AssetClassSP;
  spValHash: string;
  iassetValHash: string;
  iAssetAuthToken: AssetClassSP;
  collateralAssetAuthToken: AssetClassSP;
  interestCollectorValHash: string;
  cdpRedeemValHash: string;
  cdpAuthToken: AssetClassSP;
  cdpAssetSymbol: CurrencySymbolSP;
  biasTime: bigint;
}

export interface IAssetParamsSP {
  upgradeToken: AssetClassSP;
  versionRecordToken: AssetClassSP;
}

export interface CDPCreatorParamsSP {
  versionRecordToken: AssetClassSP;
  upgradeToken: AssetClassSP;
  iAssetAuthTk: AssetClassSP;
  collateralAssetAuthTk: AssetClassSP;
  treasuryValHash: string;
  cdpScriptHash: string;
  iassetValHash: string;
  cdpCreatorNft: AssetClassSP;
  cdpAuthTk: AssetClassSP;
  cdpAssetCs: CurrencySymbolSP;
  biasTime: bigint;
}

export function toSystemParamsAsset(asset: AssetClass): AssetClassSP {
  return [
    { unCurrencySymbol: toHex(asset.currencySymbol) },
    { unTokenName: toText(toHex(asset.tokenName)) },
  ];
}

export function fromSystemParamsAsset(asset: AssetClassSP): AssetClass {
  return {
    currencySymbol: fromHex(asset[0].unCurrencySymbol),
    tokenName: fromHex(fromText(asset[1].unTokenName)),
  };
}

export function fromSystemParamsAssetLucid(asset: AssetClassSP): AssetClassD {
  return {
    currencySymbol: asset[0].unCurrencySymbol,
    tokenName: fromText(asset[1].unTokenName),
  };
}

export function fromSystemParamsScriptRef(ref: ScriptReference): OutRef {
  return { outputIndex: ref.input.index, txHash: ref.input.transactionId };
}

export function fromSysParamsCredential(cred: ScriptCredential): Credential {
  if (cred.contents.tag === 'PublicKeyCredential') {
    return { type: 'Key', hash: cred.contents.contents };
  }
  return { type: 'Script', hash: cred.contents.contents };
}

export function fromSysParamsStakeCredential(
  cred: ScriptCredential,
): StakeCredential {
  return {
    Inline: [
      cred.contents.tag === 'PublicKeyCredential'
        ? {
            PublicKeyCredential: [cred.contents.contents],
          }
        : {
            ScriptCredential: [cred.contents.contents],
          },
    ],
  };
}
