use sqlx::PgPool;

use crate::error::Result;
use crate::models::SanctionedAddress;

pub trait AddressRepository: Send + Sync {
    /// Check if a specific address exists for a given currency. Returns the source type if found.
    fn check_address(
        &self,
        currency: &str,
        address: &str,
    ) -> impl std::future::Future<Output = Result<Option<String>>> + Send;

    /// Fetch all sanctioned addresses (optionally filtered by currency).
    fn list_all(
        &self,
        currency_filter: Option<&str>,
    ) -> impl std::future::Future<Output = Result<Vec<SanctionedAddress>>> + Send;

    /// Upsert a sanctioned address (insert if not exists).
    fn upsert(
        &self,
        currency: &str,
        address: &str,
        source_type: &str,
    ) -> impl std::future::Future<Output = Result<()>> + Send;
}

#[derive(Clone)]
pub struct PgAddressRepository {
    pool: PgPool,
}

impl PgAddressRepository {
    pub fn new(pool: PgPool) -> Self {
        Self { pool }
    }
}

impl AddressRepository for PgAddressRepository {
    async fn check_address(&self, currency: &str, address: &str) -> Result<Option<String>> {
        let row: Option<(String,)> = sqlx::query_as(
            "SELECT source_type FROM sanctioned_addresses WHERE currency = $1 AND address = $2 LIMIT 1",
        )
        .bind(currency)
        .bind(address)
        .fetch_optional(&self.pool)
        .await?;

        Ok(row.map(|(source_type,)| source_type))
    }

    async fn list_all(
        &self,
        currency_filter: Option<&str>,
    ) -> Result<Vec<SanctionedAddress>> {
        let rows = match currency_filter {
            Some(currency) => {
                sqlx::query_as::<_, (String, String, String)>(
                    "SELECT currency, address, source_type FROM sanctioned_addresses WHERE currency = $1 ORDER BY address",
                )
                .bind(currency)
                .fetch_all(&self.pool)
                .await?
            }
            None => {
                sqlx::query_as::<_, (String, String, String)>(
                    "SELECT currency, address, source_type FROM sanctioned_addresses ORDER BY currency, address",
                )
                .fetch_all(&self.pool)
                .await?
            }
        };

        Ok(rows
            .into_iter()
            .map(|(currency, address, source_type)| SanctionedAddress {
                currency,
                address,
                source_type,
            })
            .collect())
    }

    async fn upsert(
        &self,
        currency: &str,
        address: &str,
        source_type: &str,
    ) -> Result<()> {
        sqlx::query(
            r#"
            INSERT INTO sanctioned_addresses (currency, address, source_type, created_at, updated_at)
            VALUES ($1, $2, $3, NOW(), NOW())
            ON CONFLICT (currency, address) DO UPDATE SET
                source_type = EXCLUDED.source_type,
                updated_at = NOW()
            "#,
        )
        .bind(currency)
        .bind(address)
        .bind(source_type)
        .execute(&self.pool)
        .await?;

        Ok(())
    }
}
