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

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct Verification {
    pub id: Uuid,
    pub reference_number: String,
    pub user_id: Uuid,
    pub role_key: String,
    pub status: String,
    pub priority: String,
    pub case_type: String,
    pub payload: serde_json::Value,
    pub documents: serde_json::Value,
    pub notes: Option<String>,
    pub rejection_reason: Option<String>,
    pub assigned_to: Option<Uuid>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct VerificationLog {
    pub id: Uuid,
    pub verification_request_id: Uuid,
    pub action: String,
    pub acted_by_user_id: Option<Uuid>,
    pub old_status: Option<String>,
    pub new_status: Option<String>,
    pub remarks: Option<String>,
    pub created_at: DateTime<Utc>,
}

pub struct VerificationRepository;

impl VerificationRepository {
    pub async fn create(
        pool: &PgPool,
        user_id: Uuid,
        role_key: &str,
        case_type: &str,
        priority: &str,
        payload: serde_json::Value,
        documents: serde_json::Value,
    ) -> Result<Verification, sqlx::Error> {
        sqlx::query_as::<_, Verification>(
            r#"
            INSERT INTO verifications (user_id, role_key, case_type, priority, payload, documents)
            VALUES ($1, $2, $3, $4, $5, $6)
            RETURNING *
            "#
        )
        .bind(user_id)
        .bind(role_key)
        .bind(case_type)
        .bind(priority)
        .bind(payload)
        .bind(documents)
        .fetch_one(pool)
        .await
    }

    pub async fn create_approved(
        pool: &PgPool,
        user_id: Uuid,
        role_key: &str,
        case_type: &str,
        payload: serde_json::Value,
        documents: serde_json::Value,
    ) -> Result<Verification, sqlx::Error> {
        sqlx::query_as::<_, Verification>(
            r#"
            INSERT INTO verifications (user_id, role_key, case_type, priority, status, payload, documents, reviewed_at, reviewer_notes)
            VALUES ($1, $2, $3, 'MEDIUM', 'APPROVED', $4, $5, NOW(), 'Auto-approved for demo account')
            RETURNING *
            "#
        )
        .bind(user_id)
        .bind(role_key)
        .bind(case_type)
        .bind(payload)
        .bind(documents)
        .fetch_one(pool)
        .await
    }

    pub async fn get_by_id(pool: &PgPool, id: Uuid) -> Result<Option<Verification>, sqlx::Error> {
        sqlx::query_as::<_, Verification>("SELECT * FROM verifications WHERE id = $1")
            .bind(id)
            .fetch_optional(pool)
            .await
    }

    pub async fn list(
        pool: &PgPool,
        status: Option<&str>,
        case_type: Option<&str>,
        page: i64,
        limit: i64,
    ) -> Result<Vec<Verification>, sqlx::Error> {
        let offset = (page - 1) * limit;
        let mut query = "SELECT * FROM verifications WHERE 1=1".to_string();
        
        if status.is_some() { query.push_str(" AND status = $1"); }
        if case_type.is_some() { query.push_str(if status.is_some() { " AND case_type = $2" } else { " AND case_type = $1" }); }
        
        query.push_str(" ORDER BY created_at DESC LIMIT $3 OFFSET $4");
        
        sqlx::query_as::<_, Verification>(
            r#"
            SELECT * FROM verifications 
            WHERE ($1::TEXT IS NULL OR status = $1)
              AND ($2::TEXT IS NULL OR case_type = $2)
            ORDER BY 
                CASE priority WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END,
                created_at DESC
            LIMIT $3 OFFSET $4
            "#
        )
        .bind(status)
        .bind(case_type)
        .bind(limit)
        .bind(offset)
        .fetch_all(pool)
        .await
    }

    pub async fn update_status(
        pool: &PgPool,
        id: Uuid,
        new_status: &str,
        actor_id: Option<Uuid>,
        notes: Option<&str>,
        rejection_reason: Option<&str>,
    ) -> Result<Verification, sqlx::Error> {
        let mut tx = pool.begin().await?;

        // Validate actor_id exists in users table; if not, treat as NULL
        // This handles cases where the token contains a user_id from an external auth system
        let valid_actor_id = match actor_id {
            Some(uid) => {
                let exists = sqlx::query_scalar::<_, bool>("SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)")
                    .bind(uid)
                    .fetch_one(&mut *tx)
                    .await?;
                if exists { Some(uid) } else { None }
            },
            None => None,
        };

        let old = sqlx::query_as::<_, Verification>("SELECT * FROM verifications WHERE id = $1 FOR UPDATE")
            .bind(id)
            .fetch_one(&mut *tx)
            .await?;

        let updated = sqlx::query_as::<_, Verification>(
            r#"
            UPDATE verifications 
            SET status = $2, notes = COALESCE($3, notes), rejection_reason = COALESCE($4, rejection_reason), updated_at = NOW() 
            WHERE id = $1 
            RETURNING *
            "#
        )
        .bind(id)
        .bind(new_status)
        .bind(notes)
        .bind(rejection_reason)
        .fetch_one(&mut *tx)
        .await?;

        sqlx::query(
            r#"
            INSERT INTO verification_logs (verification_request_id, action, acted_by_user_id, old_status, new_status, remarks)
            VALUES ($1, 'STATUS_CHANGE', $2, $3, $4, $5)
            "#
        )
        .bind(id)
        .bind(valid_actor_id)
        .bind(&old.status)
        .bind(new_status)
        .bind(notes)
        .execute(&mut *tx)
        .await?;

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