use std::{
    str::FromStr,
    sync::{Arc, RwLock},
};

use bc_ur::{prelude::CBOR, MultipartDecoder, MultipartEncoder, UR};
use cdk::{
    mint_url::MintUrl,
    nuts::{CurrencyUnit, Proof, Token as CdkToken},
};
use cdk_common::{KeySetInfo, Proofs};
use flutter_rust_bridge::frb;

use super::error::Error;

pub struct Token {
    pub encoded: String,
    pub raw: Option<Vec<u8>>,
    pub amount: u64,
    pub mint_url: String,
}

impl Token {
    #[frb(sync)]
    pub fn parse(encoded: &str) -> Result<Self, Error> {
        let encoded = encoded.strip_prefix("cashu:").unwrap_or(encoded);
        let token = CdkToken::from_str(encoded)?;
        Token::try_from(token)
    }

    #[frb(sync)]
    pub fn from_raw_bytes(raw: Vec<u8>) -> Result<Self, Error> {
        let token = CdkToken::try_from(&raw)?;
        Token::try_from(token)
    }

    pub(crate) fn proofs(&self, mint_keysets: &[KeySetInfo]) -> Result<Proofs, Error> {
        let token: CdkToken = self.try_into()?;
        Ok(token.proofs(mint_keysets)?)
    }
}

impl std::fmt::Display for Token {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.encoded)
    }
}

impl FromStr for Token {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Error> {
        Token::parse(s)
    }
}

impl TryFrom<CdkToken> for Token {
    type Error = Error;

    fn try_from(token: CdkToken) -> Result<Self, Error> {
        match token {
            CdkToken::TokenV3(token_v3) => Ok(Token {
                encoded: token_v3.to_string(),
                raw: None,
                amount: token_v3.value()?.into(),
                mint_url: token_v3
                    .mint_urls()
                    .first()
                    .ok_or(Error::InvalidInput)?
                    .to_string(),
            }),
            CdkToken::TokenV4(token_v4) => Ok(Token {
                encoded: token_v4.to_string(),
                raw: Some(token_v4.to_raw_bytes()?),
                amount: token_v4.value()?.into(),
                mint_url: token_v4.mint_url.to_string(),
            }),
        }
    }
}

impl TryInto<CdkToken> for Token {
    type Error = Error;

    fn try_into(self) -> Result<CdkToken, Error> {
        Ok(CdkToken::from_str(&self.encoded)?)
    }
}

impl TryInto<CdkToken> for &Token {
    type Error = Error;

    fn try_into(self) -> Result<CdkToken, Error> {
        Ok(CdkToken::from_str(&self.encoded)?)
    }
}

// === Offline token creation ===

/// Create a token from raw proof data without network access.
/// Uses CDK's CdkToken::new to produce a valid V4 (cashuB) token.
/// `proofs_json` is a JSON array of proofs: [{"id":"...","amount":1,"secret":"...","C":"..."}, ...]
#[frb(sync)]
pub fn create_offline_token(
    mint_url: &str,
    proofs_json: &str,
    memo: Option<String>,
    unit: Option<String>,
) -> Result<Token, Error> {
    let mint_url = MintUrl::from_str(mint_url)?;
    let unit = unit
        .map(|u| CurrencyUnit::from_str(&u).unwrap_or(CurrencyUnit::Custom(u)))
        .unwrap_or(CurrencyUnit::Sat);
    let proofs: Vec<Proof> = serde_json::from_str(proofs_json)?;
    let token = CdkToken::new(mint_url, proofs, memo, unit);
    Token::try_from(token)
}

// === QR Multiframe (bc-ur) ===

pub struct TokenDecoder {
    decoder: Arc<RwLock<MultipartDecoder>>,
}

impl TokenDecoder {
    #[frb(sync)]
    pub fn new() -> Self {
        Self {
            decoder: Arc::new(RwLock::new(MultipartDecoder::default())),
        }
    }

    #[frb(sync)]
    pub fn is_complete(&self) -> bool {
        self.decoder
            .read()
            .map(|decoder| decoder.is_complete())
            .unwrap_or(false)
    }

    #[frb(sync)]
    pub fn receive(&self, input: String) -> Result<(), Error> {
        let mut decoder = self
            .decoder
            .write()
            .map_err(|_| Error::Ur("Token decoder lock poisoned".to_string()))?;
        decoder.receive(&input)?;
        Ok(())
    }

    #[frb(sync)]
    pub fn value(&self) -> Result<Option<Token>, Error> {
        let decoder = self
            .decoder
            .read()
            .map_err(|_| Error::Ur("Token decoder lock poisoned".to_string()))?;
        let ur = decoder.message()?;
        match ur {
            Some(ur) => {
                let bytes = ur
                    .cbor()
                    .try_byte_string()
                    .map_err(|e| Error::Ur(format!("Failed to decode CBOR byte string: {}", e)))?;
                let utf8_encoded = String::from_utf8(bytes)?;
                let token = Token::from_str(&utf8_encoded)?;
                Ok(Some(token))
            }
            None => Ok(None),
        }
    }
}

#[frb(sync)]
pub fn encode_qr_token(
    token: &Token,
    max_fragment_length: Option<usize>,
) -> Result<Vec<String>, Error> {
    let ur = UR::new("", CBOR::to_byte_string(&token.encoded))?;
    let mut encoder = MultipartEncoder::new(&ur, max_fragment_length.unwrap_or(150))?;
    let mut parts = Vec::new();
    for _ in 0..encoder.parts_count() {
        parts.push(encoder.next_part()?);
    }
    Ok(parts)
}
