// Core dependencies
import { AccountLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { AccountInfo, PublicKey } from "@solana/web3.js";

// Local imports
import {
    MEME_SOL,
    RAYDIUM_CPMM, 
    RAYDIUM_LIQUIDITY_POOL_V4,
    USDC_DECIMALS,
    USDC_MINT,
    WSOL_DECIMALS, 
    WSOL_MINT 
} from "./constants";
import { BasketsProgram } from "../idl/types";
import { Program } from "@coral-xyz/anchor";
import { PythSponsoredFeeds } from "../state/oracle";

export enum OracleType {
    Pyth,
    RaydiumCpmm,
    RaydiumLiquidityPoolV4,
    Unknown
}

export class OracleTypeUtils {
    static toU8(type: OracleType): number {
        switch (type) {
            case OracleType.Pyth: return 0;
            case OracleType.RaydiumCpmm: return 1;
            case OracleType.RaydiumLiquidityPoolV4: return 2;
            case OracleType.Unknown: return 3;
        }
    }

    static fromU8(value: number): OracleType {
        switch (value) {
            case 0: return OracleType.Pyth;
            case 1: return OracleType.RaydiumCpmm;
            case 2: return OracleType.RaydiumLiquidityPoolV4;
            default: return OracleType.Unknown;
        }
    }
}

export function validateOracle(
    program: Program<BasketsProgram>,
    tokenMint: PublicKey,
    tokenMintAccountInfo: AccountInfo<Buffer>,
    oracleAccountInfo: AccountInfo<Buffer>,
    oracleType: number,
    oracle1: PublicKey,
    oracle2: PublicKey
): string {

    const oracleTypeObj = OracleTypeUtils.fromU8(oracleType);
    // Validate token mint
    if (!tokenMintAccountInfo.owner.equals(TOKEN_PROGRAM_ID)) return "Invalid token mint owner";
    if (tokenMintAccountInfo.data.length !== 82) return "Invalid token mint account size";

    if (oracleTypeObj == OracleType.Pyth) {
        const pythSponsoredFeeds: PythSponsoredFeeds = program.coder.accounts.decode("pythSponsoredFeeds", oracleAccountInfo.data);
        let index = 0;
        for (let i = 0; i < pythSponsoredFeeds.numTokens; i++)
            if (pythSponsoredFeeds.mints[i].equals(tokenMint)) {
                index = i;
                break;
            }
        if (!pythSponsoredFeeds.mints[index].equals(tokenMint))
            return "Token mint not found in PythSponsoredFeeds";
        if (!pythSponsoredFeeds.feeds[index].equals(oracle1))
            return "Invalid pyth oracle feed";
        if (pythSponsoredFeeds.isActive[index] == 0)
            return "Pyth oracle feed is not active";
        return "OK";
    }
    if (oracleTypeObj == OracleType.RaydiumCpmm || oracleTypeObj == OracleType.RaydiumLiquidityPoolV4) {
        const accountData = oracleAccountInfo.data;
            
        // Check status for V4 pools
        if (oracleTypeObj == OracleType.RaydiumLiquidityPoolV4) {
            const status = accountData[0];
            if (status !== 6) return "Invalid status (Using OrderBook)";
        }

        // Get token vault and mint offsets based on pool type
        const [vaultOffset0, vaultOffset1, mintOffset0, mintOffset1] = 
            oracleTypeObj == OracleType.RaydiumCpmm ? [72, 104, 168, 200] : [336, 368, 400, 432];

        // Extract token vaults and mints
        let tokenVault0 = new PublicKey(accountData.slice(vaultOffset0, vaultOffset0 + 32));
        let tokenVault1 = new PublicKey(accountData.slice(vaultOffset1, vaultOffset1 + 32));
        let tokenMint0 = new PublicKey(accountData.slice(mintOffset0, mintOffset0 + 32));
        let tokenMint1 = new PublicKey(accountData.slice(mintOffset1, mintOffset1 + 32));

        // Swap if needed to ensure tokenMint1 is SOL/USDC
        if (tokenMint0.equals(WSOL_MINT) || tokenMint0.equals(USDC_MINT) || tokenMint0.equals(MEME_SOL)) {
            [tokenMint0, tokenMint1] = [tokenMint1, tokenMint0];
            [tokenVault0, tokenVault1] = [tokenVault1, tokenVault0];
        }

        // Validate owner program
        const expectedOwner = oracleTypeObj == OracleType.RaydiumCpmm ? RAYDIUM_CPMM : RAYDIUM_LIQUIDITY_POOL_V4;

        if (!oracleAccountInfo.owner.equals(expectedOwner)) return "Invalid oracle account owner";

        // Validate token mints and vaults
        if (!tokenMint0.equals(tokenMint)) return "Invalid base token mint";
        if (!tokenMint1.equals(WSOL_MINT) && !tokenMint1.equals(USDC_MINT) && !tokenMint1.equals(MEME_SOL)) {
            return "Invalid quote token mint (Should be SOL, MEME-SOL or USDC)";
        }
        if (!tokenVault0.equals(oracle1)) return "Invalid base token vault";
        if (!tokenVault1.equals(oracle2)) return "Invalid quote token vault";
        return "OK";
    }

    return "Invalid oracle type";
}

export interface OraclePrice {
    sellPrice: number;
    avgPrice: number;
    buyPrice: number;
    age: number;
    sellValue: number;
}

export function loadOraclePrice(
    tokenDecimals: number,
    oracleType: number,
    oracleAccount1: AccountInfo<Buffer> | null,
    oracleAccount2: AccountInfo<Buffer> | null,
    solPrice: number,
    usdcPrice: number,
    tokenAmount: number
): OraclePrice {
    let avgPrice: number;
    let confidence: number;
    let sellValue: number;

    if (!oracleAccount1) { throw new Error(); }

    switch (OracleTypeUtils.fromU8(oracleType)) {
        case OracleType.Pyth: {
            const accountData = oracleAccount1.data;
            const price = accountData.readBigInt64LE(73);
            const exp = accountData.readInt32LE(89);
            avgPrice = Number(price) * 10 ** exp;
            confidence = avgPrice / 100;
            const sellPrice = avgPrice - confidence;
            const powNum = 10 ** tokenDecimals;
            sellValue =tokenAmount * sellPrice / powNum;
            break;
        }

        case OracleType.RaydiumCpmm:
        case OracleType.RaydiumLiquidityPoolV4: {
            if (!oracleAccount2) { throw new Error(); }

            const baseData = AccountLayout.decode(oracleAccount1.data);
            const quoteData = AccountLayout.decode(oracleAccount2.data);

            const x = parseInt(baseData.amount.toString());
            const y = parseInt(quoteData.amount.toString());
            const quoteMint = quoteData.mint;

            const [quoteDecimals, quotePrice] = (quoteMint.equals(WSOL_MINT) || quoteMint.equals(MEME_SOL))
                ? [WSOL_DECIMALS, solPrice]
                : [USDC_DECIMALS, usdcPrice];

            const priceRaw = (y * quotePrice) / x;
            const tokenDecimalsPow = 10 ** tokenDecimals;
            const quoteDecimalsPow = 10 ** quoteDecimals;
            avgPrice = (priceRaw * tokenDecimalsPow) / quoteDecimalsPow;
            confidence = avgPrice / 100;

            const baseAmount = (tokenAmount * y) / (x + tokenAmount);
            const powNum = 10 ** tokenDecimals;
            sellValue = baseAmount * quotePrice / powNum;

            break;
        }
        default:
            throw new Error();
    }

    return {
        sellPrice: avgPrice - confidence,
        avgPrice,
        buyPrice: avgPrice + confidence,
        age: 0,
        sellValue
    };
}
