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

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct Requirement {
    pub id: Uuid,
    pub profession_key: String,
    pub title: String,
    pub description: String,
    pub location: String,
    pub budget_inr: Option<i32>,
    pub required_date: Option<chrono::NaiveDate>,
    pub extra_data_json: Option<serde_json::Value>,
    pub status: String,
    pub rejection_reason: Option<String>,
    pub request_count: i32,
    pub accepted_count: i32,
    pub expires_at: Option<DateTime<Utc>>,
    pub approved_at: Option<DateTime<Utc>>,
    pub approved_by: Option<Uuid>,
    pub created_at: DateTime<Utc>,
    pub created_by_user_id: Option<Uuid>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CreateRequirementPayload {
    pub created_by_user_id: Uuid,
    pub profession_key: String,
    pub title: String,
    pub description: String,
    pub location: String,
    pub budget_inr: Option<i32>,
    pub required_date: Option<chrono::NaiveDate>,
    pub extra_data_json: Option<serde_json::Value>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateRequirementPayload {
    pub title: Option<String>,
    pub description: Option<String>,
    pub location: Option<String>,
    pub budget_inr: Option<i32>,
    pub required_date: Option<chrono::NaiveDate>,
    pub extra_data_json: Option<serde_json::Value>,
}

pub struct RequirementRepository;

impl RequirementRepository {
    pub async fn create(
        pool: &PgPool,
        payload: CreateRequirementPayload,
    ) -> Result<Requirement, sqlx::Error> {
        let req = sqlx::query_as::<_, Requirement>(
            r#"
            INSERT INTO leads (
                created_by_user_id, profession_key, title, description, location,
                budget_inr, required_date, extra_data_json
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
            RETURNING *
            "#,
        )
        .bind(payload.created_by_user_id)
        .bind(payload.profession_key)
        .bind(payload.title)
        .bind(payload.description)
        .bind(payload.location)
        .bind(payload.budget_inr)
        .bind(payload.required_date)
        .bind(payload.extra_data_json)
        .fetch_one(pool)
        .await?;

        Ok(req)
    }

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

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

        Ok(reqs)
    }

    pub async fn update(
        pool: &PgPool,
        id: Uuid,
        payload: UpdateRequirementPayload,
    ) -> Result<Requirement, sqlx::Error> {
        let req = sqlx::query_as::<_, Requirement>(
            r#"
            UPDATE leads SET
                title = COALESCE($1, title),
                description = COALESCE($2, description),
                location = COALESCE($3, location),
                budget_inr = COALESCE($4, budget_inr),
                required_date = COALESCE($5, required_date),
                extra_data_json = COALESCE($6, extra_data_json),
                updated_at = NOW()
            WHERE id = $7
            RETURNING *
            "#,
        )
        .bind(payload.title)
        .bind(payload.description)
        .bind(payload.location)
        .bind(payload.budget_inr)
        .bind(payload.required_date)
        .bind(payload.extra_data_json)
        .bind(id)
        .fetch_one(pool)
        .await?;

        Ok(req)
    }

    pub async fn update_status(
        pool: &PgPool,
        id: Uuid,
        status: &str,
    ) -> Result<Requirement, sqlx::Error> {
        let req = sqlx::query_as::<_, Requirement>(
            "UPDATE leads SET status = $1, updated_at = NOW() WHERE id = $2 RETURNING *",
        )
        .bind(status)
        .bind(id)
        .fetch_one(pool)
        .await?;
        Ok(req)
    }

    pub async fn increment_request_count(pool: &PgPool, id: Uuid) -> Result<(), sqlx::Error> {
        sqlx::query("UPDATE leads SET request_count = request_count + 1 WHERE id = $1")
            .bind(id)
            .execute(pool)
            .await?;
        Ok(())
    }

    pub async fn increment_accepted_count(pool: &PgPool, id: Uuid) -> Result<(), sqlx::Error> {
        sqlx::query(
            "UPDATE leads SET accepted_count = accepted_count + 1 WHERE id = $1",
        )
        .bind(id)
        .execute(pool)
        .await?;
        Ok(())
    }

    pub async fn increment_accepted_count_and_get(
        pool: &PgPool,
        id: Uuid,
    ) -> Result<Requirement, sqlx::Error> {
        sqlx::query_as::<_, Requirement>(
            r#"
            UPDATE leads
            SET accepted_count = accepted_count + 1, updated_at = NOW()
            WHERE id = $1
            RETURNING *
            "#,
        )
        .bind(id)
        .fetch_one(pool)
        .await
    }

    pub async fn approve(
        pool: &PgPool,
        id: Uuid,
        admin_user_id: Uuid,
    ) -> Result<Requirement, sqlx::Error> {
        sqlx::query_as::<_, Requirement>(
            r#"
            UPDATE leads
            SET status = 'OPEN', approved_at = NOW(), approved_by = $1, rejection_reason = NULL, updated_at = NOW()
            WHERE id = $2
            RETURNING *
            "#,
        )
        .bind(admin_user_id)
        .bind(id)
        .fetch_one(pool)
        .await
    }

    pub async fn reject(
        pool: &PgPool,
        id: Uuid,
        reason: Option<String>,
    ) -> Result<Requirement, sqlx::Error> {
        sqlx::query_as::<_, Requirement>(
            r#"
            UPDATE leads
            SET status = 'REJECTED', rejection_reason = $1, approved_at = NULL, approved_by = NULL, updated_at = NOW()
            WHERE id = $2
            RETURNING *
            "#,
        )
        .bind(reason)
        .bind(id)
        .fetch_one(pool)
        .await
    }
}
