use crate::AppState;
use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::IntoResponse,
    routing::{get, patch, post},
    Json, Router,
};
use contracts::auth_middleware::AuthUser;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

// ── Routers ───────────────────────────────────────────────────────────────────

pub fn coupons_router() -> Router<AppState> {
    Router::new()
        .route("/", get(list_coupons).post(create_coupon))
        .route("/{id}", patch(update_coupon).delete(delete_coupon))
        .route("/validate", post(validate_coupon))
}

pub fn discounts_router() -> Router<AppState> {
    Router::new()
        .route("/", get(list_discounts).post(create_discount))
        .route("/{id}", patch(update_discount))
}

// ── Coupon DTOs ───────────────────────────────────────────────────────────────

#[derive(Serialize)]
struct CouponDto {
    id: Uuid,
    code: String,
    title: Option<String>,
    #[serde(rename = "type")]
    discount_type: String,
    value: i32,
    min_order_amount: i32,
    usage_limit: Option<i32>,
    used_count: i32,
    role_keys: Vec<String>,
    applies_to: String,
    is_active: bool,
    valid_until: Option<chrono::DateTime<chrono::Utc>>,
}

#[derive(Deserialize)]
struct CreateCouponBody {
    code: String,
    title: Option<String>,
    #[serde(rename = "type")]
    discount_type: Option<String>,
    value: Option<i32>,
    min_order_amount: Option<i32>,
    max_uses: Option<i32>,
    role_keys: Option<Vec<String>>,
    applies_to: Option<String>,
}

#[derive(Deserialize)]
struct PatchCouponBody {
    code: Option<String>,
    title: Option<String>,
    #[serde(rename = "type")]
    discount_type: Option<String>,
    value: Option<i32>,
    min_order_amount: Option<i32>,
    max_uses: Option<i32>,
    role_keys: Option<Vec<String>>,
    is_active: Option<bool>,
}

// ── Discount DTOs ─────────────────────────────────────────────────────────────

#[derive(Serialize)]
struct DiscountDto {
    id: Uuid,
    title: String,
    scope: String,
    role_key: Option<String>,
    package_id: Option<Uuid>,
    #[serde(rename = "type")]
    discount_type: String,
    value: i32,
    is_active: bool,
}

#[derive(Deserialize)]
struct CreateDiscountBody {
    title: String,
    scope: Option<String>,
    role_key: Option<String>,
    package_id: Option<Uuid>,
    #[serde(rename = "type")]
    discount_type: Option<String>,
    value: Option<i32>,
}

#[derive(Deserialize)]
struct PatchDiscountBody {
    title: Option<String>,
    scope: Option<String>,
    role_key: Option<String>,
    package_id: Option<Uuid>,
    #[serde(rename = "type")]
    discount_type: Option<String>,
    value: Option<i32>,
    is_active: Option<bool>,
}

// ── FromRow structs ──────────────────────────────────────────────────────────

#[derive(sqlx::FromRow)]
struct CouponRow {
    id: Uuid,
    code: String,
    title: Option<String>,
    discount_type: String,
    discount_value: i32,
    min_order_amount: i32,
    max_uses: Option<i32>,
    uses_count: i32,
    role_keys: Vec<String>,
    applies_to: String,
    is_active: bool,
    valid_until: Option<chrono::DateTime<chrono::Utc>>,
}

#[derive(sqlx::FromRow)]
struct ExistingCouponRow {
    code: String,
    title: Option<String>,
    discount_type: String,
    discount_value: i32,
    min_order_amount: i32,
    max_uses: Option<i32>,
    role_keys: Vec<String>,
    is_active: bool,
}

#[derive(sqlx::FromRow)]
#[allow(dead_code)]
struct ValidateCouponRow {
    id: Uuid,
    code: String,
    discount_type: String,
    discount_value: i32,
    min_order_amount: i32,
    max_uses: Option<i32>,
    role_keys: Vec<String>,
    valid_until: Option<chrono::DateTime<chrono::Utc>>,
    is_active: bool,
}

#[derive(sqlx::FromRow)]
struct DiscountRow {
    id: Uuid,
    title: String,
    scope: String,
    role_key: Option<String>,
    package_id: Option<Uuid>,
    discount_type: String,
    discount_value: i32,
    is_active: bool,
}

#[derive(sqlx::FromRow)]
struct ExistingDiscountRow {
    title: String,
    scope: String,
    role_key: Option<String>,
    package_id: Option<Uuid>,
    discount_type: String,
    discount_value: i32,
    is_active: bool,
}

// ── Coupon handlers ───────────────────────────────────────────────────────────

async fn list_coupons(
    _auth: AuthUser,
    State(state): State<AppState>,
) -> impl IntoResponse {
    let rows = sqlx::query_as::<_, CouponRow>(
        r#"
        SELECT id, code, title, discount_type, discount_value, min_order_amount,
               max_uses, uses_count, role_keys, applies_to, is_active, valid_until
        FROM coupons
        ORDER BY created_at DESC
        "#,
    )
    .fetch_all(&state.pool)
    .await;

    match rows {
        Ok(rows) => {
            let dtos: Vec<CouponDto> = rows
                .into_iter()
                .map(|r| CouponDto {
                    id: r.id,
                    code: r.code,
                    title: r.title,
                    discount_type: r.discount_type,
                    value: r.discount_value,
                    min_order_amount: r.min_order_amount,
                    usage_limit: r.max_uses,
                    used_count: r.uses_count,
                    role_keys: r.role_keys,
                    applies_to: r.applies_to,
                    is_active: r.is_active,
                    valid_until: r.valid_until,
                })
                .collect();
            (StatusCode::OK, Json(serde_json::json!({ "coupons": dtos }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to list coupons: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to load coupons" }))).into_response()
        }
    }
}

async fn create_coupon(
    _auth: AuthUser,
    State(state): State<AppState>,
    Json(body): Json<CreateCouponBody>,
) -> impl IntoResponse {
    let discount_type = body.discount_type.unwrap_or_else(|| "PERCENT".to_string());
    let value = body.value.unwrap_or(0);
    let min_order = body.min_order_amount.unwrap_or(0);
    let applies_to = body.applies_to.unwrap_or_else(|| "ALL".to_string());
    let role_keys: Vec<String> = body.role_keys.unwrap_or_default();
    let code = body.code.to_uppercase();

    let row = sqlx::query_as::<_, CouponRow>(
        r#"
        INSERT INTO coupons (code, title, discount_type, discount_value, min_order_amount,
                             max_uses, role_keys, applies_to)
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
        RETURNING id, code, title, discount_type, discount_value, min_order_amount,
                  max_uses, uses_count, role_keys, applies_to, is_active, valid_until
        "#,
    )
    .bind(&code)
    .bind(&body.title)
    .bind(&discount_type)
    .bind(value)
    .bind(min_order)
    .bind(body.max_uses)
    .bind(&role_keys)
    .bind(&applies_to)
    .fetch_one(&state.pool)
    .await;

    match row {
        Ok(r) => {
            let dto = CouponDto {
                id: r.id,
                code: r.code,
                title: r.title,
                discount_type: r.discount_type,
                value: r.discount_value,
                min_order_amount: r.min_order_amount,
                usage_limit: r.max_uses,
                used_count: r.uses_count,
                role_keys: r.role_keys,
                applies_to: r.applies_to,
                is_active: r.is_active,
                valid_until: r.valid_until,
            };
            (StatusCode::CREATED, Json(serde_json::json!(dto))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to create coupon: {e}");
            if e.to_string().contains("unique") {
                (StatusCode::CONFLICT, Json(serde_json::json!({ "error": "Coupon code already exists" }))).into_response()
            } else {
                (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to create coupon" }))).into_response()
            }
        }
    }
}

async fn update_coupon(
    _auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
    Json(body): Json<PatchCouponBody>,
) -> impl IntoResponse {
    let existing = sqlx::query_as::<_, ExistingCouponRow>(
        "SELECT code, title, discount_type, discount_value, min_order_amount, max_uses, role_keys, is_active FROM coupons WHERE id = $1",
    )
    .bind(id)
    .fetch_optional(&state.pool)
    .await;

    let existing = match existing {
        Ok(Some(r)) => r,
        Ok(None) => return (StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "Coupon not found" }))).into_response(),
        Err(e) => {
            tracing::error!("DB error: {e}");
            return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "DB error" }))).into_response();
        }
    };

    let code = body.code.map(|c| c.to_uppercase()).unwrap_or(existing.code);
    let title = body.title.or(existing.title);
    let discount_type = body.discount_type.unwrap_or(existing.discount_type);
    let value = body.value.unwrap_or(existing.discount_value);
    let min_order = body.min_order_amount.unwrap_or(existing.min_order_amount);
    let max_uses = body.max_uses.or(existing.max_uses);
    let role_keys = body.role_keys.unwrap_or(existing.role_keys);
    let is_active = body.is_active.unwrap_or(existing.is_active);

    let result = sqlx::query(
        r#"
        UPDATE coupons
        SET code = $1, title = $2, discount_type = $3, discount_value = $4,
            min_order_amount = $5, max_uses = $6, role_keys = $7, is_active = $8
        WHERE id = $9
        "#,
    )
    .bind(code)
    .bind(title)
    .bind(discount_type)
    .bind(value)
    .bind(min_order)
    .bind(max_uses)
    .bind(&role_keys)
    .bind(is_active)
    .bind(id)
    .execute(&state.pool)
    .await;

    match result {
        Ok(_) => (StatusCode::OK, Json(serde_json::json!({ "ok": true }))).into_response(),
        Err(e) => {
            tracing::error!("Failed to update coupon {id}: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to update coupon" }))).into_response()
        }
    }
}

async fn delete_coupon(
    _auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    let result = sqlx::query("DELETE FROM coupons WHERE id = $1")
        .bind(id)
        .execute(&state.pool)
        .await;

    match result {
        Ok(r) if r.rows_affected() == 0 => {
            (StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "Coupon not found" }))).into_response()
        }
        Ok(_) => StatusCode::NO_CONTENT.into_response(),
        Err(e) => {
            tracing::error!("Failed to delete coupon {id}: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to delete coupon" }))).into_response()
        }
    }
}

// ── Discount handlers ─────────────────────────────────────────────────────────

async fn list_discounts(
    _auth: AuthUser,
    State(state): State<AppState>,
) -> impl IntoResponse {
    let rows = sqlx::query_as::<_, DiscountRow>(
        r#"
        SELECT id, title, scope, role_key, package_id, discount_type, discount_value, is_active
        FROM discounts
        ORDER BY created_at DESC
        "#,
    )
    .fetch_all(&state.pool)
    .await;

    match rows {
        Ok(rows) => {
            let dtos: Vec<DiscountDto> = rows
                .into_iter()
                .map(|r| DiscountDto {
                    id: r.id,
                    title: r.title,
                    scope: r.scope,
                    role_key: r.role_key,
                    package_id: r.package_id,
                    discount_type: r.discount_type,
                    value: r.discount_value,
                    is_active: r.is_active,
                })
                .collect();
            (StatusCode::OK, Json(serde_json::json!({ "discounts": dtos }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to list discounts: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to load discounts" }))).into_response()
        }
    }
}

async fn create_discount(
    _auth: AuthUser,
    State(state): State<AppState>,
    Json(body): Json<CreateDiscountBody>,
) -> impl IntoResponse {
    let scope = body.scope.unwrap_or_else(|| "ROLE".to_string());
    let discount_type = body.discount_type.unwrap_or_else(|| "PERCENT".to_string());
    let value = body.value.unwrap_or(0);

    let row = sqlx::query_as::<_, DiscountRow>(
        r#"
        INSERT INTO discounts (title, scope, role_key, package_id, discount_type, discount_value)
        VALUES ($1, $2, $3, $4, $5, $6)
        RETURNING id, title, scope, role_key, package_id, discount_type, discount_value, is_active
        "#,
    )
    .bind(&body.title)
    .bind(&scope)
    .bind(&body.role_key)
    .bind(body.package_id)
    .bind(&discount_type)
    .bind(value)
    .fetch_one(&state.pool)
    .await;

    match row {
        Ok(r) => {
            let dto = DiscountDto {
                id: r.id,
                title: r.title,
                scope: r.scope,
                role_key: r.role_key,
                package_id: r.package_id,
                discount_type: r.discount_type,
                value: r.discount_value,
                is_active: r.is_active,
            };
            (StatusCode::CREATED, Json(serde_json::json!(dto))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to create discount: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to create discount" }))).into_response()
        }
    }
}

async fn update_discount(
    _auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
    Json(body): Json<PatchDiscountBody>,
) -> impl IntoResponse {
    let existing = sqlx::query_as::<_, ExistingDiscountRow>(
        "SELECT title, scope, role_key, package_id, discount_type, discount_value, is_active FROM discounts WHERE id = $1",
    )
    .bind(id)
    .fetch_optional(&state.pool)
    .await;

    let existing = match existing {
        Ok(Some(r)) => r,
        Ok(None) => return (StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "Discount not found" }))).into_response(),
        Err(e) => {
            tracing::error!("DB error: {e}");
            return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "DB error" }))).into_response();
        }
    };

    let title = body.title.unwrap_or(existing.title);
    let scope = body.scope.unwrap_or(existing.scope);
    let role_key = body.role_key.or(existing.role_key);
    let package_id = body.package_id.or(existing.package_id);
    let discount_type = body.discount_type.unwrap_or(existing.discount_type);
    let value = body.value.unwrap_or(existing.discount_value);
    let is_active = body.is_active.unwrap_or(existing.is_active);

    let result = sqlx::query(
        r#"
        UPDATE discounts
        SET title = $1, scope = $2, role_key = $3, package_id = $4,
            discount_type = $5, discount_value = $6, is_active = $7
        WHERE id = $8
        "#,
    )
    .bind(title)
    .bind(scope)
    .bind(role_key)
    .bind(package_id)
    .bind(discount_type)
    .bind(value)
    .bind(is_active)
    .bind(id)
    .execute(&state.pool)
    .await;

    match result {
        Ok(_) => (StatusCode::OK, Json(serde_json::json!({ "updated": true }))).into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e.to_string() }))).into_response(),
    }
}

#[derive(Deserialize)]
struct ValidateCouponPayload {
    coupon_code: String,
    role_key: String,
    package_price_inr: i32,
}

#[derive(Serialize)]
struct ValidateCouponResponse {
    valid: bool,
    discount_type: Option<String>,
    discount_value: Option<i32>,
    final_price_inr: i32,
    message: String,
}

async fn validate_coupon(
    _auth: AuthUser,
    State(state): State<AppState>,
    Json(payload): Json<ValidateCouponPayload>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let code = payload.coupon_code.trim().to_uppercase();

    // Fetch coupon
    let coupon = sqlx::query_as::<_, ValidateCouponRow>(
        r#"
        SELECT id, code, discount_type, discount_value, min_order_amount,
               max_uses, role_keys, valid_until, is_active
        FROM coupons
        WHERE code = $1
        "#,
    )
    .bind(&code)
    .fetch_optional(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    let coupon = match coupon {
        Some(c) => c,
        None => {
            return Ok((
                StatusCode::OK,
                Json(ValidateCouponResponse {
                    valid: false,
                    discount_type: None,
                    discount_value: None,
                    final_price_inr: payload.package_price_inr,
                    message: "Coupon not found".to_string(),
                }),
            ));
        }
    };

    // Check active
    if !coupon.is_active {
        return Ok((
            StatusCode::OK,
            Json(ValidateCouponResponse {
                valid: false,
                discount_type: None,
                discount_value: None,
                final_price_inr: payload.package_price_inr,
                message: "Coupon is inactive".to_string(),
            }),
        ));
    }

    // Check expiry
    if let Some(valid_until) = coupon.valid_until {
        if valid_until < chrono::Utc::now() {
            return Ok((
                StatusCode::OK,
                Json(ValidateCouponResponse {
                    valid: false,
                    discount_type: None,
                    discount_value: None,
                    final_price_inr: payload.package_price_inr,
                    message: "Coupon has expired".to_string(),
                }),
            ));
        }
    }

    // Check role restriction
    if !coupon.role_keys.is_empty() && !coupon.role_keys.iter().any(|r| r.eq_ignore_ascii_case(&payload.role_key)) {
        return Ok((
            StatusCode::OK,
            Json(ValidateCouponResponse {
                valid: false,
                discount_type: None,
                discount_value: None,
                final_price_inr: payload.package_price_inr,
                message: "Coupon not valid for your role".to_string(),
            }),
        ));
    }

    // Check minimum order amount
    if payload.package_price_inr < coupon.min_order_amount {
        return Ok((
            StatusCode::OK,
            Json(ValidateCouponResponse {
                valid: false,
                discount_type: None,
                discount_value: None,
                final_price_inr: payload.package_price_inr,
                message: format!("Minimum order amount ₹{} required", coupon.min_order_amount / 100),
            }),
        ));
    }

    // Check usage limit if set
    if let Some(max_uses) = coupon.max_uses {
        let count: i64 = sqlx::query_scalar::<_, i64>(
            "SELECT COUNT(*) FROM coupon_uses WHERE coupon_id = $1",
        )
        .bind(coupon.id)
        .fetch_one(&state.pool)
        .await
        .unwrap_or(0);
        if count >= max_uses as i64 {
            return Ok((
                StatusCode::OK,
                Json(ValidateCouponResponse {
                    valid: false,
                    discount_type: None,
                    discount_value: None,
                    final_price_inr: payload.package_price_inr,
                    message: "Coupon usage limit reached".to_string(),
                }),
            ));
        }
    }

    // Compute final price
    let final_price = match coupon.discount_type.as_str() {
        "PERCENT" => {
            let discount = ((payload.package_price_inr as f64) * (coupon.discount_value as f64) / 100.0).round() as i32;
            (payload.package_price_inr - discount).max(0)
        }
        "FIXED" => (payload.package_price_inr - coupon.discount_value).max(0),
        _ => payload.package_price_inr,
    };

    Ok((
        StatusCode::OK,
        Json(ValidateCouponResponse {
            valid: true,
            discount_type: Some(coupon.discount_type),
            discount_value: Some(coupon.discount_value),
            final_price_inr: final_price,
            message: "Coupon applied".to_string(),
        }),
    ))
}
