use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, PgPool};
use uuid::Uuid;

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct Professional {
    pub id: Uuid,
    pub user_id: Uuid,
    pub role_key: String,
    pub status: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

use super::requirement::Requirement;

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct PortfolioItem {
    pub id: Uuid,
    pub user_role_profile_id: Uuid,
    pub title: String,
    pub description: Option<String>,
    pub tags: Option<Vec<String>>,
    pub display_order: Option<i32>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct Service {
    pub id: Uuid,
    pub user_role_profile_id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub price: i32,
    pub duration_minutes: Option<i32>,
    pub is_active: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct Wallet {
    pub id: Uuid,
    pub user_id: Uuid,
    pub balance: i32,
    pub reserved: i32,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct TracecoinLedgerEntry {
    pub id: Uuid,
    pub wallet_id: Uuid,
    pub transaction_type: String,
    pub amount: i32,
    pub reference_type: Option<String>,
    pub reference_id: Option<Uuid>,
    pub created_at: DateTime<Utc>,
}

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct Invoice {
    pub id: Uuid,
    pub payment_id: Uuid,
    pub user_id: Uuid,
    pub invoice_number: String,
    pub subtotal: i32,
    pub gst_amount: i32,
    pub total: i32,
    pub status: String,
    pub issued_at: DateTime<Utc>,
    pub file_url: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CreatePortfolioItemPayload {
    pub title: String,
    pub description: Option<String>,
    pub tags: Option<Vec<String>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct UpdatePortfolioItemPayload {
    pub title: Option<String>,
    pub description: Option<String>,
    pub tags: Option<Vec<String>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CreateServicePayload {
    pub name: String,
    pub description: Option<String>,
    pub price: i32,
    pub duration_minutes: Option<i32>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateServicePayload {
    pub name: Option<String>,
    pub description: Option<String>,
    pub price: Option<i32>,
    pub duration_minutes: Option<i32>,
    pub is_active: Option<bool>,
}

pub struct ProfessionalRepository;

impl ProfessionalRepository {
    pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result<Professional, sqlx::Error> {
        sqlx::query_as::<_, Professional>(
            "SELECT id, user_id, role_key, status, created_at, updated_at FROM user_role_profiles WHERE user_id = $1 AND role_key != 'CUSTOMER' AND role_key != 'COMPANY'",
        )
        .bind(user_id)
        .fetch_one(pool)
        .await
    }

    pub async fn get_marketplace(
        pool: &PgPool,
        profession_key: &str,
        page: i64,
        limit: i64,
    ) -> Result<Vec<Requirement>, sqlx::Error> {
        let offset = (page - 1) * limit;
        sqlx::query_as::<_, Requirement>(
            r#"
            SELECT * FROM leads
            WHERE profession_key = $1 AND status = 'OPEN' AND (expires_at IS NULL OR expires_at > NOW())
            ORDER BY created_at DESC
            LIMIT $2 OFFSET $3
            "#,
        )
        .bind(profession_key)
        .bind(limit)
        .bind(offset)
        .fetch_all(pool)
        .await
    }

    pub async fn get_portfolio(pool: &PgPool, user_role_profile_id: Uuid) -> Result<Vec<PortfolioItem>, sqlx::Error> {
        sqlx::query_as::<_, PortfolioItem>(
            "SELECT * FROM portfolio_items WHERE user_role_profile_id = $1 ORDER BY display_order, created_at DESC",
        )
        .bind(user_role_profile_id)
        .fetch_all(pool)
        .await
    }

    pub async fn get_services(pool: &PgPool, user_role_profile_id: Uuid) -> Result<Vec<Service>, sqlx::Error> {
        sqlx::query_as::<_, Service>(
            "SELECT * FROM services WHERE user_role_profile_id = $1 AND is_active = true ORDER BY name ASC",
        )
        .bind(user_role_profile_id)
        .fetch_all(pool)
        .await
    }

    pub async fn get_wallet(pool: &PgPool, user_id: Uuid) -> Result<Wallet, sqlx::Error> {
        sqlx::query_as::<_, Wallet>(
            "SELECT * FROM tracecoin_wallets WHERE user_id = $1",
        )
        .bind(user_id)
        .fetch_one(pool)
        .await
    }

    pub async fn ensure_wallet(pool: &PgPool, user_id: Uuid) -> Result<(), sqlx::Error> {
        sqlx::query(
            r#"
            INSERT INTO tracecoin_wallets (user_id, balance, reserved)
            VALUES ($1, 0, 0)
            ON CONFLICT (user_id) DO NOTHING
            "#,
        )
        .bind(user_id)
        .execute(pool)
        .await?;
        Ok(())
    }

    pub async fn get_user_id_by_user_role_profile_id(
        pool: &PgPool,
        user_role_profile_id: Uuid,
    ) -> Result<Option<Uuid>, sqlx::Error> {
        let row = sqlx::query_scalar::<_, Uuid>(
            "SELECT user_id FROM user_role_profiles WHERE id = $1",
        )
        .bind(user_role_profile_id)
        .fetch_optional(pool)
        .await?;
        Ok(row)
    }

    pub async fn create_portfolio_item(
        pool: &PgPool,
        user_role_profile_id: Uuid,
        payload: CreatePortfolioItemPayload,
    ) -> Result<PortfolioItem, sqlx::Error> {
        sqlx::query_as::<_, PortfolioItem>(
            r#"
            INSERT INTO portfolio_items (user_role_profile_id, title, description, tags)
            VALUES ($1, $2, $3, COALESCE($4::text[], '{}'))
            RETURNING *
            "#,
        )
        .bind(user_role_profile_id)
        .bind(payload.title)
        .bind(payload.description)
        .bind(payload.tags)
        .fetch_one(pool)
        .await
    }

    pub async fn update_portfolio_item(
        pool: &PgPool,
        user_role_profile_id: Uuid,
        id: Uuid,
        payload: UpdatePortfolioItemPayload,
    ) -> Result<Option<PortfolioItem>, sqlx::Error> {
        let row = sqlx::query_as::<_, PortfolioItem>(
            r#"
            UPDATE portfolio_items
            SET
                title = COALESCE($1, title),
                description = COALESCE($2, description),
                tags = COALESCE($3, tags),
                updated_at = NOW()
            WHERE id = $4 AND user_role_profile_id = $5
            RETURNING *
            "#,
        )
        .bind(payload.title)
        .bind(payload.description)
        .bind(payload.tags)
        .bind(id)
        .bind(user_role_profile_id)
        .fetch_optional(pool)
        .await?;
        Ok(row)
    }

    pub async fn delete_portfolio_item(
        pool: &PgPool,
        user_role_profile_id: Uuid,
        id: Uuid,
    ) -> Result<bool, sqlx::Error> {
        let result = sqlx::query(
            "DELETE FROM portfolio_items WHERE id = $1 AND user_role_profile_id = $2",
        )
        .bind(id)
        .bind(user_role_profile_id)
        .execute(pool)
        .await?;
        Ok(result.rows_affected() > 0)
    }

    pub async fn create_service(
        pool: &PgPool,
        user_role_profile_id: Uuid,
        payload: CreateServicePayload,
    ) -> Result<Service, sqlx::Error> {
        sqlx::query_as::<_, Service>(
            r#"
            INSERT INTO services (user_role_profile_id, name, description, price, duration_minutes)
            VALUES ($1, $2, $3, $4, $5)
            RETURNING *
            "#,
        )
        .bind(user_role_profile_id)
        .bind(payload.name)
        .bind(payload.description)
        .bind(payload.price)
        .bind(payload.duration_minutes)
        .fetch_one(pool)
        .await
    }

    pub async fn update_service(
        pool: &PgPool,
        user_role_profile_id: Uuid,
        id: Uuid,
        payload: UpdateServicePayload,
    ) -> Result<Option<Service>, sqlx::Error> {
        let row = sqlx::query_as::<_, Service>(
            r#"
            UPDATE services
            SET
                name = COALESCE($1, name),
                description = COALESCE($2, description),
                price = COALESCE($3, price),
                duration_minutes = COALESCE($4, duration_minutes),
                is_active = COALESCE($5, is_active),
                updated_at = NOW()
            WHERE id = $6 AND user_role_profile_id = $7
            RETURNING *
            "#,
        )
        .bind(payload.name)
        .bind(payload.description)
        .bind(payload.price)
        .bind(payload.duration_minutes)
        .bind(payload.is_active)
        .bind(id)
        .bind(user_role_profile_id)
        .fetch_optional(pool)
        .await?;
        Ok(row)
    }

    pub async fn delete_service(
        pool: &PgPool,
        user_role_profile_id: Uuid,
        id: Uuid,
    ) -> Result<bool, sqlx::Error> {
        let result = sqlx::query("DELETE FROM services WHERE id = $1 AND user_role_profile_id = $2")
            .bind(id)
            .bind(user_role_profile_id)
            .execute(pool)
            .await?;
        Ok(result.rows_affected() > 0)
    }

    pub async fn list_wallet_ledger(
        pool: &PgPool,
        user_id: Uuid,
        page: i64,
        limit: i64,
    ) -> Result<Vec<TracecoinLedgerEntry>, sqlx::Error> {
        let offset = (page - 1) * limit;
        sqlx::query_as::<_, TracecoinLedgerEntry>(
            r#"
            SELECT l.*
            FROM tracecoin_ledger l
            INNER JOIN tracecoin_wallets w ON w.id = l.wallet_id
            WHERE w.user_id = $1
            ORDER BY l.created_at DESC
            LIMIT $2 OFFSET $3
            "#,
        )
        .bind(user_id)
        .bind(limit)
        .bind(offset)
        .fetch_all(pool)
        .await
    }

    pub async fn list_wallet_invoices(
        pool: &PgPool,
        user_id: Uuid,
        page: i64,
        limit: i64,
    ) -> Result<Vec<Invoice>, sqlx::Error> {
        let offset = (page - 1) * limit;
        sqlx::query_as::<_, Invoice>(
            r#"
            SELECT *
            FROM invoices
            WHERE user_id = $1
            ORDER BY issued_at DESC
            LIMIT $2 OFFSET $3
            "#,
        )
        .bind(user_id)
        .bind(limit)
        .bind(offset)
        .fetch_all(pool)
        .await
    }

    pub async fn get_invoice_by_id_for_user(
        pool: &PgPool,
        user_id: Uuid,
        id: Uuid,
    ) -> Result<Option<Invoice>, sqlx::Error> {
        sqlx::query_as::<_, Invoice>(
            "SELECT * FROM invoices WHERE id = $1 AND user_id = $2",
        )
        .bind(id)
        .bind(user_id)
        .fetch_optional(pool)
        .await
    }

    pub async fn try_reserve_tracecoins(
        pool: &PgPool,
        user_id: Uuid,
        amount: i32,
        reference_id: Uuid,
    ) -> Result<bool, sqlx::Error> {
        let mut tx = pool.begin().await?;

        sqlx::query(
            r#"
            INSERT INTO tracecoin_wallets (user_id, balance, reserved)
            VALUES ($1, 0, 0)
            ON CONFLICT (user_id) DO NOTHING
            "#,
        )
        .bind(user_id)
        .execute(&mut *tx)
        .await?;

        let wallet = sqlx::query_as::<_, Wallet>(
            "SELECT * FROM tracecoin_wallets WHERE user_id = $1 FOR UPDATE",
        )
        .bind(user_id)
        .fetch_one(&mut *tx)
        .await?;

        if wallet.balance < amount {
            tx.rollback().await?;
            return Ok(false);
        }

        sqlx::query(
            r#"
            UPDATE tracecoin_wallets
            SET balance = balance - $1, reserved = reserved + $1, updated_at = NOW()
            WHERE id = $2
            "#,
        )
        .bind(amount)
        .bind(wallet.id)
        .execute(&mut *tx)
        .await?;

        sqlx::query(
            r#"
            INSERT INTO tracecoin_ledger (wallet_id, type, amount, reason, reference_id)
            VALUES ($1, 'RESERVE', $2, 'LEAD_REQUEST', $3)
            "#,
        )
        .bind(wallet.id)
        .bind(amount)
        .bind(reference_id)
        .execute(&mut *tx)
        .await?;

        tx.commit().await?;
        Ok(true)
    }

    pub async fn try_debit_reserved_tracecoins(
        pool: &PgPool,
        user_id: Uuid,
        amount: i32,
        reference_id: Uuid,
    ) -> Result<bool, sqlx::Error> {
        let mut tx = pool.begin().await?;

        let wallet = sqlx::query_as::<_, Wallet>(
            "SELECT * FROM tracecoin_wallets WHERE user_id = $1 FOR UPDATE",
        )
        .bind(user_id)
        .fetch_optional(&mut *tx)
        .await?;

        let Some(wallet) = wallet else {
            tx.rollback().await?;
            return Ok(false);
        };

        if wallet.reserved < amount {
            tx.rollback().await?;
            return Ok(false);
        }

        sqlx::query(
            r#"
            UPDATE tracecoin_wallets
            SET reserved = reserved - $1, updated_at = NOW()
            WHERE id = $2
            "#,
        )
        .bind(amount)
        .bind(wallet.id)
        .execute(&mut *tx)
        .await?;

        sqlx::query(
            r#"
            INSERT INTO tracecoin_ledger (wallet_id, type, amount, reason, reference_id)
            VALUES ($1, 'DEBIT', $2, 'LEAD_ACCEPTED', $3)
            "#,
        )
        .bind(wallet.id)
        .bind(amount)
        .bind(reference_id)
        .execute(&mut *tx)
        .await?;

        tx.commit().await?;
        Ok(true)
    }

    pub async fn try_release_reserved_tracecoins(
        pool: &PgPool,
        user_id: Uuid,
        amount: i32,
        reference_id: Uuid,
        reason: &str,
    ) -> Result<bool, sqlx::Error> {
        let mut tx = pool.begin().await?;

        let wallet = sqlx::query_as::<_, Wallet>(
            "SELECT * FROM tracecoin_wallets WHERE user_id = $1 FOR UPDATE",
        )
        .bind(user_id)
        .fetch_optional(&mut *tx)
        .await?;

        let Some(wallet) = wallet else {
            tx.rollback().await?;
            return Ok(false);
        };

        if wallet.reserved < amount {
            tx.rollback().await?;
            return Ok(false);
        }

        sqlx::query(
            r#"
            UPDATE tracecoin_wallets
            SET reserved = reserved - $1, balance = balance + $1, updated_at = NOW()
            WHERE id = $2
            "#,
        )
        .bind(amount)
        .bind(wallet.id)
        .execute(&mut *tx)
        .await?;

        sqlx::query(
            r#"
            INSERT INTO tracecoin_ledger (wallet_id, type, amount, reason, reference_id)
            VALUES ($1, 'RELEASE', $2, $3, $4)
            "#,
        )
        .bind(wallet.id)
        .bind(amount)
        .bind(reason)
        .bind(reference_id)
        .execute(&mut *tx)
        .await?;

        tx.commit().await?;
        Ok(true)
    }

    pub async fn submit_for_verification(
        pool: &PgPool,
        user_id: Uuid,
        profession_key: &str,
    ) -> Result<Professional, sqlx::Error> {
        let prof = sqlx::query_as::<_, Professional>(
            "SELECT id, user_id, role_key, status, created_at, updated_at FROM user_role_profiles WHERE user_id = $1 AND role_key = $2",
        )
        .bind(user_id)
        .bind(profession_key)
        .fetch_one(pool)
        .await?;

        if prof.status == "PENDING_REVIEW" || prof.status == "APPROVED" {
            return Err(sqlx::Error::Protocol(format!("Professional profile is already {}", prof.status).into()));
        }

        let prof = sqlx::query_as::<_, Professional>(
            r#"
            UPDATE user_role_profiles
            SET status = 'PENDING_REVIEW', updated_at = NOW()
            WHERE user_id = $1 AND role_key = $2
            RETURNING id, user_id, role_key, status, created_at, updated_at
            "#,
        )
        .bind(user_id)
        .bind(profession_key)
        .fetch_one(pool)
        .await?;

        Ok(prof)
    }
}
