// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Bridging `TryFrom` / `From` impls that convert API-layer types
//! (from `rialo-api-types`) into CDK generated types
//! (from `crate::generated::types`).
//!
//! These conversions are used by `HttpRpcClient` when the RPC response
//! carries API types that must be surfaced as CDK types to callers.

use std::str::FromStr;

use rialo_api_types::messages::get_transaction::{
    Instruction as ApiInstruction, MessageHeader as ApiMessageHeader,
    Transaction as ApiTransaction, TransactionMessage as ApiTransactionMessage,
    TransactionStatusMetadata as ApiTransactionStatusMetadata,
};
use serde_json::Value;

use crate::{
    error::{RialoError, RpcErrorDetails},
    generated::types::{
        self as cdk, CompiledInstruction, MessageHeader, TransactionData, TransactionMessage,
        TransactionStatusMetadata, TransactionWithMeta,
    },
};

// ═══════════════════════════════════════════════════════════════════════════
// API types → CDK types  (used by `get_transaction`)
// ═══════════════════════════════════════════════════════════════════════════

impl From<ApiInstruction> for CompiledInstruction {
    fn from(ix: ApiInstruction) -> Self {
        Self {
            program_id_index: ix.program_id_index,
            accounts: ix.accounts,
            data: ix.data,
        }
    }
}

impl From<ApiMessageHeader> for MessageHeader {
    fn from(h: ApiMessageHeader) -> Self {
        Self {
            num_required_signatures: h.num_required_signatures,
            num_readonly_signed_accounts: h.num_readonly_signed_accounts,
            num_readonly_unsigned_accounts: h.num_readonly_unsigned_accounts,
        }
    }
}

impl TryFrom<ApiTransactionMessage> for TransactionMessage {
    type Error = RialoError;

    fn try_from(msg: ApiTransactionMessage) -> Result<Self, Self::Error> {
        let account_keys = msg
            .account_keys
            .iter()
            .map(|k| cdk::PublicKey::from_str(k))
            .collect::<std::result::Result<Vec<_>, _>>()
            .map_err(|e| {
                RialoError::Rpc(RpcErrorDetails::from_message(format!(
                    "Invalid account key: {e}"
                )))
            })?;

        Ok(Self {
            header: msg.header.into(),
            account_keys,
            instructions: msg.instructions.into_iter().map(Into::into).collect(),
        })
    }
}

impl TryFrom<ApiTransaction> for TransactionData {
    type Error = RialoError;

    fn try_from(tx: ApiTransaction) -> Result<Self, Self::Error> {
        let signatures = tx
            .signatures
            .iter()
            .map(|s| cdk::Signature::from_str(s))
            .collect::<std::result::Result<Vec<_>, _>>()
            .map_err(|e| {
                RialoError::Rpc(RpcErrorDetails::from_message(format!(
                    "Invalid transaction signature: {e}"
                )))
            })?;

        Ok(Self {
            signatures,
            message: tx.message.try_into()?,
            valid_from: tx.valid_from,
        })
    }
}

impl From<ApiTransactionStatusMetadata> for TransactionStatusMetadata {
    fn from(meta: ApiTransactionStatusMetadata) -> Self {
        Self {
            err: meta.err.map(|e| e.to_string()),
            fee: meta.fee,
            log_messages: meta.log_messages,
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// serde_json::Value → CDK types  (used by `get_confirmed_block`)
//
// These trait impls document which types are constructible from raw JSON
// and make the conversion discoverable through the type system.
// ═══════════════════════════════════════════════════════════════════════════

/// Fallible conversion — requires `accounts`, `data`, and `programIdIndex` fields.
impl TryFrom<&Value> for CompiledInstruction {
    type Error = ();

    fn try_from(v: &Value) -> std::result::Result<Self, ()> {
        Ok(Self {
            accounts: serde_json::from_value(v.get("accounts").ok_or(())?.clone())
                .map_err(|_| ())?,
            data: v
                .get("data")
                .and_then(|d| d.as_str())
                .ok_or(())?
                .to_string(),
            program_id_index: v.get("programIdIndex").and_then(|p| p.as_u64()).ok_or(())? as u8,
        })
    }
}

/// Infallible conversion — missing fields default to `0`.
impl From<&Value> for MessageHeader {
    fn from(v: &Value) -> Self {
        Self {
            num_required_signatures: v
                .get("numRequiredSignatures")
                .and_then(|v| v.as_u64())
                .unwrap_or(0) as u8,
            num_readonly_signed_accounts: v
                .get("numReadonlySignedAccounts")
                .and_then(|v| v.as_u64())
                .unwrap_or(0) as u8,
            num_readonly_unsigned_accounts: v
                .get("numReadonlyUnsignedAccounts")
                .and_then(|v| v.as_u64())
                .unwrap_or(0) as u8,
        }
    }
}

/// Infallible conversion — missing fields default to `None` / `0`.
impl From<&Value> for TransactionStatusMetadata {
    fn from(v: &Value) -> Self {
        Self {
            err: v.get("err").and_then(|e| {
                if e.is_null() {
                    None
                } else {
                    Some(e.to_string())
                }
            }),
            fee: v.get("fee").and_then(|f| f.as_u64()).unwrap_or(0),
            log_messages: v
                .get("logMessages")
                .and_then(|l| serde_json::from_value(l.clone()).ok()),
        }
    }
}

/// Fallible conversion — requires the `transaction.message` substructure.
impl TryFrom<&Value> for TransactionWithMeta {
    type Error = ();

    fn try_from(v: &Value) -> std::result::Result<Self, ()> {
        let tx_obj = v.get("transaction").ok_or(())?;

        let sigs: Vec<cdk::Signature> = tx_obj
            .get("signatures")
            .and_then(|s| s.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|s| s.as_str().and_then(|s| cdk::Signature::from_str(s).ok()))
                    .collect()
            })
            .unwrap_or_default();

        let msg = tx_obj.get("message").ok_or(())?;

        let account_keys: Vec<cdk::PublicKey> = msg
            .get("accountKeys")
            .and_then(|a| a.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|k| k.as_str().and_then(|k| cdk::PublicKey::from_str(k).ok()))
                    .collect()
            })
            .unwrap_or_default();

        let instructions: Vec<CompiledInstruction> = msg
            .get("instructions")
            .and_then(|i| i.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|ix| CompiledInstruction::try_from(ix).ok())
                    .collect()
            })
            .unwrap_or_default();

        let header = msg.get("header").unwrap_or(&Value::Null);

        let tx_data = TransactionData {
            signatures: sigs,
            message: TransactionMessage {
                account_keys,
                header: MessageHeader::from(header),
                instructions,
            },
            valid_from: tx_obj
                .get("validFrom")
                .and_then(|v| v.as_i64())
                .unwrap_or(0),
        };

        let meta = v.get("meta").map(TransactionStatusMetadata::from);

        Ok(Self {
            transaction: tx_data,
            meta,
        })
    }
}
