use async_trait::async_trait;
use num_bigint::BigUint;
use strum::IntoStaticStr;
use tonlib_core::cell::{ArcCell, BagOfCells, CellBuilder, CellSlice, TonCellError};
use tonlib_core::TonAddress;

use crate::contract::{MapCellError, MapStackError, TonContractError, TonContractInterface};
use crate::meta::MetaDataContent;
use crate::types::TvmStackEntry;

#[derive(PartialEq, Eq, Debug, Clone)]
pub struct JettonData {
    pub total_supply: BigUint,
    pub mintable: bool,
    pub admin_address: TonAddress,
    pub content: MetaDataContent,
    pub wallet_code: ArcCell,
}

#[derive(IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
enum JettonMasterMethods {
    GetJettonData,
    GetWalletAddress,
}

#[async_trait]
pub trait JettonMasterContract: TonContractInterface {
    async fn get_jetton_data(&self) -> Result<JettonData, TonContractError> {
        const JETTON_DATA_STACK_ELEMENTS: usize = 5;
        let method: &str = JettonMasterMethods::GetJettonData.into();
        let address = self.address().clone();

        let res = self.run_get_method(method, Vec::new()).await?;

        let stack = res.stack;
        if stack.len() >= JETTON_DATA_STACK_ELEMENTS {
            let total_supply = stack[0].get_biguint().map_stack_error(method, &address)?;
            let mintable = stack[1].get_bool().map_stack_error(method, &address)?;
            let admin_address = stack[2].get_address().map_stack_error(method, &address)?;
            let cell = stack[3].get_cell().map_stack_error(method, &address)?;
            let content = read_jetton_metadata_content(cell).map_cell_error(method, &address)?;
            let wallet_code = stack[4].get_cell().map_stack_error(method, &address)?;
            Ok(JettonData {
                total_supply,
                mintable,
                admin_address,
                content,
                wallet_code,
            })
        } else {
            Err(TonContractError::InvalidMethodResultStackSize {
                method: method.to_string(),
                address: self.address().clone(),
                actual: stack.len(),
                expected: JETTON_DATA_STACK_ELEMENTS,
            })
        }
    }

    async fn get_wallet_address(
        &self,
        owner_address: &TonAddress,
    ) -> Result<TonAddress, TonContractError> {
        let method: &'static str = JettonMasterMethods::GetWalletAddress.into();
        let address = self.address().clone();
        let cell = CellBuilder::new()
            .store_address(owner_address)
            .map_cell_error(method, owner_address)?
            .build()
            .map_cell_error(method, owner_address)?;
        let cell_slice = CellSlice::full_cell(cell).map_cell_error(method, owner_address)?;
        let slice = TvmStackEntry::Slice(cell_slice);
        let res = self.run_get_method(method, vec![slice]).await?;
        let stack = res.stack;
        if stack.len() == 1 {
            stack[0].get_address().map_stack_error(method, &address)
        } else {
            Err(TonContractError::InvalidMethodResultStackSize {
                method: method.to_string(),
                address: self.address().clone(),
                actual: stack.len(),
                expected: 1,
            })
        }
    }
}

impl<T> JettonMasterContract for T where T: TonContractInterface {}

fn read_jetton_metadata_content(cell: ArcCell) -> Result<MetaDataContent, TonCellError> {
    let mut parser = cell.parser();
    let content_representation = parser.load_byte()?;
    match content_representation {
        0 => {
            let dict = parser.load_dict_snake_format()?;
            Ok(MetaDataContent::Internal { dict })
        }
        1 => {
            let remaining_bytes = parser.remaining_bytes();
            let uri = parser.load_utf8(remaining_bytes)?;
            Ok(MetaDataContent::External { uri })
        }
        _ => Ok(MetaDataContent::Unsupported {
            boc: BagOfCells::from_root(cell.as_ref().clone()),
        }),
    }
}
