//! Payment Processing

use crate::{AccountId, SynorDecimal};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};

/// Payment method
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PaymentMethod {
    /// Direct SYNOR transfer
    SynorTransfer,
    /// Prepaid account balance
    PrepaidBalance,
    /// Credit/promotional balance
    CreditBalance,
    /// Smart contract escrow
    SmartContract,
    /// Cross-chain bridge payment
    CrossChain,
    /// Fiat on-ramp (converted to SYNOR)
    FiatOnRamp,
}

impl std::fmt::Display for PaymentMethod {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PaymentMethod::SynorTransfer => write!(f, "SYNOR Transfer"),
            PaymentMethod::PrepaidBalance => write!(f, "Prepaid Balance"),
            PaymentMethod::CreditBalance => write!(f, "Credit Balance"),
            PaymentMethod::SmartContract => write!(f, "Smart Contract"),
            PaymentMethod::CrossChain => write!(f, "Cross-Chain"),
            PaymentMethod::FiatOnRamp => write!(f, "Fiat On-Ramp"),
        }
    }
}

/// Payment status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PaymentStatus {
    /// Payment initiated
    Pending,
    /// Payment being processed
    Processing,
    /// Payment confirmed
    Confirmed,
    /// Payment completed successfully
    Completed,
    /// Payment failed
    Failed,
    /// Payment refunded
    Refunded,
}

impl PaymentStatus {
    /// Check if this is a terminal status
    pub fn is_terminal(&self) -> bool {
        matches!(self, Self::Completed | Self::Failed | Self::Refunded)
    }

    /// Check if payment was successful
    pub fn is_success(&self) -> bool {
        matches!(self, Self::Completed)
    }
}

/// Payment record
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Payment {
    /// Unique payment ID
    pub id: String,
    /// Account making the payment
    pub account_id: AccountId,
    /// Invoice being paid (optional)
    pub invoice_id: Option<String>,
    /// Payment amount in SYNOR
    pub amount: SynorDecimal,
    /// Payment method used
    pub method: PaymentMethod,
    /// Payment status
    pub status: PaymentStatus,
    /// Transaction hash (for on-chain payments)
    pub transaction_hash: Option<String>,
    /// Block number (for on-chain payments)
    pub block_number: Option<u64>,
    /// Date created
    pub created_at: DateTime<Utc>,
    /// Date completed
    pub completed_at: Option<DateTime<Utc>>,
    /// Failure reason (if failed)
    pub failure_reason: Option<String>,
    /// Additional metadata
    pub metadata: std::collections::HashMap<String, String>,
}

impl Payment {
    /// Create a new payment
    pub fn new(account_id: impl Into<String>, amount: SynorDecimal, method: PaymentMethod) -> Self {
        Self {
            id: generate_payment_id(),
            account_id: account_id.into(),
            invoice_id: None,
            amount,
            method,
            status: PaymentStatus::Pending,
            transaction_hash: None,
            block_number: None,
            created_at: Utc::now(),
            completed_at: None,
            failure_reason: None,
            metadata: std::collections::HashMap::new(),
        }
    }

    /// Associate with invoice
    pub fn with_invoice(mut self, invoice_id: impl Into<String>) -> Self {
        self.invoice_id = Some(invoice_id.into());
        self
    }

    /// Add transaction hash
    pub fn with_transaction(mut self, hash: impl Into<String>) -> Self {
        self.transaction_hash = Some(hash.into());
        self
    }

    /// Add block number
    pub fn with_block(mut self, block: u64) -> Self {
        self.block_number = Some(block);
        self
    }

    /// Add metadata
    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.metadata.insert(key.into(), value.into());
        self
    }

    /// Mark payment as processing
    pub fn mark_processing(&mut self) {
        self.status = PaymentStatus::Processing;
    }

    /// Mark payment as confirmed (awaiting finality)
    pub fn mark_confirmed(&mut self, tx_hash: String) {
        self.status = PaymentStatus::Confirmed;
        self.transaction_hash = Some(tx_hash);
    }

    /// Mark payment as completed
    pub fn mark_completed(&mut self) {
        self.status = PaymentStatus::Completed;
        self.completed_at = Some(Utc::now());
    }

    /// Mark payment as failed
    pub fn mark_failed(&mut self, reason: impl Into<String>) {
        self.status = PaymentStatus::Failed;
        self.failure_reason = Some(reason.into());
        self.completed_at = Some(Utc::now());
    }

    /// Mark payment as refunded
    pub fn mark_refunded(&mut self) {
        self.status = PaymentStatus::Refunded;
        self.completed_at = Some(Utc::now());
    }

    /// Check if payment is complete
    pub fn is_complete(&self) -> bool {
        self.status == PaymentStatus::Completed
    }

    /// Check if payment is pending
    pub fn is_pending(&self) -> bool {
        matches!(
            self.status,
            PaymentStatus::Pending | PaymentStatus::Processing | PaymentStatus::Confirmed
        )
    }
}

/// Generate unique payment ID
fn generate_payment_id() -> String {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    format!("pay_{:x}", nanos)
}

/// Payment processor for handling different payment methods
pub struct PaymentProcessor;

impl PaymentProcessor {
    /// Process a SYNOR transfer payment
    pub async fn process_synor_transfer(
        payment: &mut Payment,
        from_address: &str,
        to_address: &str,
    ) -> Result<(), PaymentError> {
        // In production, this would:
        // 1. Create and broadcast a transaction
        // 2. Wait for confirmation
        // 3. Update payment status

        payment.mark_processing();

        // Simulate transaction
        let tx_hash = format!("0x{:x}000000000000000000000000000000000000000000000000000000000000",
            std::time::SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs());

        payment.mark_confirmed(tx_hash);

        // Add addresses to metadata
        payment.metadata.insert("from".to_string(), from_address.to_string());
        payment.metadata.insert("to".to_string(), to_address.to_string());

        payment.mark_completed();

        Ok(())
    }

    /// Process prepaid balance payment
    pub async fn process_prepaid(
        payment: &mut Payment,
        available_balance: SynorDecimal,
    ) -> Result<(), PaymentError> {
        if available_balance < payment.amount {
            payment.mark_failed("Insufficient prepaid balance");
            return Err(PaymentError::InsufficientBalance);
        }

        payment.mark_processing();
        payment.mark_completed();

        Ok(())
    }

    /// Validate payment request
    pub fn validate(payment: &Payment) -> Result<(), PaymentError> {
        if payment.amount <= Decimal::ZERO {
            return Err(PaymentError::InvalidAmount);
        }

        if payment.account_id.is_empty() {
            return Err(PaymentError::InvalidAccount);
        }

        Ok(())
    }
}

/// Payment processing errors
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PaymentError {
    /// Invalid payment amount
    InvalidAmount,
    /// Invalid account
    InvalidAccount,
    /// Insufficient balance
    InsufficientBalance,
    /// Transaction failed
    TransactionFailed(String),
    /// Network error
    NetworkError(String),
}

impl std::fmt::Display for PaymentError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PaymentError::InvalidAmount => write!(f, "Invalid payment amount"),
            PaymentError::InvalidAccount => write!(f, "Invalid account"),
            PaymentError::InsufficientBalance => write!(f, "Insufficient balance"),
            PaymentError::TransactionFailed(msg) => write!(f, "Transaction failed: {}", msg),
            PaymentError::NetworkError(msg) => write!(f, "Network error: {}", msg),
        }
    }
}

impl std::error::Error for PaymentError {}

#[cfg(test)]
mod tests {
    use super::*;
    use rust_decimal_macros::dec;

    #[test]
    fn test_payment_creation() {
        let payment = Payment::new("account_123", dec!(50), PaymentMethod::SynorTransfer)
            .with_invoice("inv_456");

        assert_eq!(payment.account_id, "account_123");
        assert_eq!(payment.amount, dec!(50));
        assert_eq!(payment.invoice_id, Some("inv_456".to_string()));
        assert_eq!(payment.status, PaymentStatus::Pending);
    }

    #[test]
    fn test_payment_flow() {
        let mut payment = Payment::new("test", dec!(100), PaymentMethod::SynorTransfer);

        assert!(payment.is_pending());

        payment.mark_processing();
        assert!(payment.is_pending());

        payment.mark_confirmed("0x123".to_string());
        assert!(payment.is_pending());

        payment.mark_completed();
        assert!(payment.is_complete());
        assert!(payment.completed_at.is_some());
    }

    #[test]
    fn test_payment_failure() {
        let mut payment = Payment::new("test", dec!(100), PaymentMethod::SynorTransfer);

        payment.mark_failed("Insufficient funds");
        assert!(!payment.is_complete());
        assert_eq!(payment.failure_reason, Some("Insufficient funds".to_string()));
    }

    #[tokio::test]
    async fn test_prepaid_processing() {
        let mut payment = Payment::new("test", dec!(50), PaymentMethod::PrepaidBalance);

        // Sufficient balance
        let result = PaymentProcessor::process_prepaid(&mut payment, dec!(100)).await;
        assert!(result.is_ok());
        assert!(payment.is_complete());

        // Insufficient balance
        let mut payment2 = Payment::new("test", dec!(150), PaymentMethod::PrepaidBalance);
        let result = PaymentProcessor::process_prepaid(&mut payment2, dec!(100)).await;
        assert!(matches!(result, Err(PaymentError::InsufficientBalance)));
    }
}
