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

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

/// Public (user-facing) — list active packages for their role
pub fn public_packages_router() -> Router<AppState> {
    Router::new().route("/", get(public_list_packages))
}

/// Admin CRUD
pub fn packages_router() -> Router<AppState> {
    Router::new()
        .route("/", get(list_packages).post(create_package))
        .route("/{id}", patch(update_package).delete(delete_package))
}

pub fn reports_router() -> Router<AppState> {
    Router::new()
        .route("/users", get(report_users))
        .route("/revenue", get(report_revenue))
}

// ── Package DTOs ──────────────────────────────────────────────────────────────

#[derive(Serialize)]
struct PackageDto {
    id: Uuid,
    name: String,
    role_key: String,
    // Also expose as 'role' for admin UI compatibility
    role: String,
    package_type: String,
    tracecoins_amount: i32,
    // Also expose as 'tracecoin_amount' for admin UI compatibility
    tracecoin_amount: i32,
    price_inr: i32,
    description: Option<String>,
    is_active: bool,
}

#[derive(Deserialize)]
struct CreatePackageBody {
    name: String,
    // Accept either role_key or role
    role_key: Option<String>,
    role: Option<String>,
    package_type: Option<String>,
    // Accept either tracecoins_amount or tracecoin_amount
    tracecoins_amount: Option<i32>,
    tracecoin_amount: Option<i32>,
    price_inr: i32,
    description: Option<String>,
}

#[derive(Deserialize)]
struct PatchPackageBody {
    name: Option<String>,
    role_key: Option<String>,
    role: Option<String>,
    package_type: Option<String>,
    tracecoins_amount: Option<i32>,
    tracecoin_amount: Option<i32>,
    price_inr: Option<i32>,
    description: Option<String>,
    is_active: Option<bool>,
}

// ── Report types ──────────────────────────────────────────────────────────────

#[derive(Deserialize)]
struct DateRangeQuery {
    from: Option<String>,
    to: Option<String>,
}

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

#[derive(sqlx::FromRow)]
struct PackageRow {
    id: Uuid,
    name: String,
    role_key: String,
    package_type: String,
    tracecoins_amount: i32,
    price_inr: i32,
    description: Option<String>,
    is_active: bool,
}

#[derive(sqlx::FromRow)]
struct ExistingPackageRow {
    name: String,
    role_key: String,
    package_type: String,
    tracecoins_amount: i32,
    price_inr: i32,
    description: Option<String>,
    is_active: bool,
}

// ── Package handlers ──────────────────────────────────────────────────────────

#[derive(Deserialize)]
struct PackageQuery {
    role: Option<String>,
    #[serde(rename = "roleKey", alias = "role_key")]
    role_key: Option<String>,
}

async fn public_list_packages(
    State(state): State<AppState>,
    Query(params): Query<PackageQuery>,
) -> impl IntoResponse {
    let requested_role = params
        .role
        .or(params.role_key)
        .map(|r| r.trim().to_uppercase())
        .filter(|r| !r.is_empty() && r != "PROFESSIONAL");

    let rows = sqlx::query_as::<_, PackageRow>(
        r#"
        SELECT id, name, role_key, package_type, tracecoins_amount, price_inr, description, is_active
        FROM pricing_packages
        WHERE is_active = true
          AND ($1::text IS NULL OR role_key = $1 OR role_key = 'ALL')
        ORDER BY role_key, price_inr
        "#,
    )
    .bind(requested_role)
    .fetch_all(&state.pool)
    .await;

    match rows {
        Ok(rows) => {
            let dtos: Vec<PackageDto> = rows
                .into_iter()
                .map(|r| PackageDto {
                    id: r.id,
                    name: r.name.clone(),
                    role_key: r.role_key.clone(),
                    role: r.role_key.clone(),
                    package_type: r.package_type,
                    tracecoins_amount: r.tracecoins_amount,
                    tracecoin_amount: r.tracecoins_amount,
                    price_inr: r.price_inr,
                    description: r.description,
                    is_active: r.is_active,
                })
                .collect();
            (StatusCode::OK, Json(serde_json::json!({ "packages": dtos }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to list packages: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to load packages" }))).into_response()
        }
    }
}

async fn list_packages(
    _auth: AuthUser,
    State(state): State<AppState>,
) -> impl IntoResponse {
    let rows = sqlx::query_as::<_, PackageRow>(
        r#"
        SELECT id, name, role_key, package_type, tracecoins_amount, price_inr, description, is_active
        FROM pricing_packages
        ORDER BY role_key, price_inr
        "#,
    )
    .fetch_all(&state.pool)
    .await;

    match rows {
        Ok(rows) => {
            let dtos: Vec<PackageDto> = rows
                .into_iter()
                .map(|r| PackageDto {
                    id: r.id,
                    name: r.name.clone(),
                    role_key: r.role_key.clone(),
                    role: r.role_key.clone(),
                    package_type: r.package_type,
                    tracecoins_amount: r.tracecoins_amount,
                    tracecoin_amount: r.tracecoins_amount,
                    price_inr: r.price_inr,
                    description: r.description,
                    is_active: r.is_active,
                })
                .collect();
            (StatusCode::OK, Json(serde_json::json!({ "packages": dtos }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to list packages: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to load packages" }))).into_response()
        }
    }
}

async fn create_package(
    _auth: AuthUser,
    State(state): State<AppState>,
    Json(body): Json<CreatePackageBody>,
) -> impl IntoResponse {
    let package_type = body.package_type.unwrap_or_else(|| "TRACECOIN_BUNDLE".to_string());
    // Accept tracecoin_amount (admin UI) or tracecoins_amount
    let tracecoins_amount = body.tracecoins_amount.or(body.tracecoin_amount).unwrap_or(0);
    // Accept role (admin UI) or role_key
    let role_key = body.role_key.or(body.role).unwrap_or_else(|| "ALL".to_string());

    let row = sqlx::query_as::<_, PackageRow>(
        r#"
        INSERT INTO pricing_packages (name, role_key, package_type, tracecoins_amount, price_inr, description)
        VALUES ($1, $2, $3, $4, $5, $6)
        RETURNING id, name, role_key, package_type, tracecoins_amount, price_inr, description, is_active
        "#,
    )
    .bind(&body.name)
    .bind(&role_key)
    .bind(&package_type)
    .bind(tracecoins_amount)
    .bind(body.price_inr)
    .bind(&body.description)
    .fetch_one(&state.pool)
    .await;

    match row {
        Ok(r) => {
            let dto = PackageDto {
                id: r.id,
                name: r.name.clone(),
                role_key: r.role_key.clone(),
                role: r.role_key.clone(),
                package_type: r.package_type,
                tracecoins_amount: r.tracecoins_amount,
                tracecoin_amount: r.tracecoins_amount,
                price_inr: r.price_inr,
                description: r.description,
                is_active: r.is_active,
            };
            (StatusCode::CREATED, Json(serde_json::json!(dto))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to create package: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to create package" }))).into_response()
        }
    }
}

async fn update_package(
    _auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
    Json(body): Json<PatchPackageBody>,
) -> impl IntoResponse {
    let existing = sqlx::query_as::<_, ExistingPackageRow>(
        "SELECT name, role_key, package_type, tracecoins_amount, price_inr, description, is_active FROM pricing_packages 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": "Package 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 name = body.name.unwrap_or(existing.name);
    let role_key = body.role_key.or(body.role).unwrap_or(existing.role_key);
    let package_type = body.package_type.unwrap_or(existing.package_type);
    let tracecoins_amount = body.tracecoins_amount.or(body.tracecoin_amount).unwrap_or(existing.tracecoins_amount);
    let price_inr = body.price_inr.unwrap_or(existing.price_inr);
    let description = body.description.or(existing.description);
    let is_active = body.is_active.unwrap_or(existing.is_active);

    let result = sqlx::query(
        r#"
        UPDATE pricing_packages
        SET name = $1, role_key = $2, package_type = $3, tracecoins_amount = $4,
            price_inr = $5, description = $6, is_active = $7
        WHERE id = $8
        "#,
    )
    .bind(name)
    .bind(role_key)
    .bind(package_type)
    .bind(tracecoins_amount)
    .bind(price_inr)
    .bind(description)
    .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 package {id}: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to update package" }))).into_response()
        }
    }
}

async fn delete_package(
    _auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    let result = sqlx::query("UPDATE pricing_packages SET is_active = false 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": "Package not found" }))).into_response()
        }
        Ok(_) => StatusCode::NO_CONTENT.into_response(),
        Err(e) => {
            tracing::error!("Failed to deactivate package {id}: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to delete package" }))).into_response()
        }
    }
}

// ── Report handlers ───────────────────────────────────────────────────────────

async fn report_users(
    _auth: AuthUser,
    State(state): State<AppState>,
    Query(params): Query<DateRangeQuery>,
) -> impl IntoResponse {
    let from = params.from.as_deref().unwrap_or("2000-01-01");
    let to = params.to.as_deref().unwrap_or("2099-12-31");

    let from_ts = match chrono::NaiveDate::parse_from_str(from, "%Y-%m-%d") {
        Ok(d) => d.and_hms_opt(0, 0, 0).unwrap().and_utc(),
        Err(_) => {
            return (StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid from date" }))).into_response();
        }
    };
    let to_ts = match chrono::NaiveDate::parse_from_str(to, "%Y-%m-%d") {
        Ok(d) => d.and_hms_opt(23, 59, 59).unwrap().and_utc(),
        Err(_) => {
            return (StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid to date" }))).into_response();
        }
    };

    let total_users: i64 = sqlx::query_scalar::<_, i64>(
        "SELECT COUNT(*) FROM users WHERE created_at >= $1 AND created_at <= $2",
    )
    .bind(from_ts)
    .bind(to_ts)
    .fetch_one(&state.pool)
    .await
    .unwrap_or(0);

    let new_users: i64 = sqlx::query_scalar::<_, i64>(
        "SELECT COUNT(*) FROM users WHERE created_at >= $1 AND created_at <= $2 AND created_at >= NOW() - INTERVAL '30 days'",
    )
    .bind(from_ts)
    .bind(to_ts)
    .fetch_one(&state.pool)
    .await
    .unwrap_or(0);

    // Active = email-verified users registered in range
    let active_users: i64 = sqlx::query_scalar::<_, i64>(
        "SELECT COUNT(*) FROM users WHERE created_at >= $1 AND created_at <= $2 AND email_verified = true",
    )
    .bind(from_ts)
    .bind(to_ts)
    .fetch_one(&state.pool)
    .await
    .unwrap_or(0);

    (StatusCode::OK, Json(serde_json::json!({
        "total_users": total_users,
        "new_users": new_users,
        "active_users": active_users,
        "from": from,
        "to": to
    }))).into_response()
}

async fn report_revenue(
    _auth: AuthUser,
    State(state): State<AppState>,
    Query(params): Query<DateRangeQuery>,
) -> impl IntoResponse {
    let from = params.from.as_deref().unwrap_or("2000-01-01");
    let to = params.to.as_deref().unwrap_or("2099-12-31");

    let from_ts = match chrono::NaiveDate::parse_from_str(from, "%Y-%m-%d") {
        Ok(d) => d.and_hms_opt(0, 0, 0).unwrap().and_utc(),
        Err(_) => {
            return (StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid from date" }))).into_response();
        }
    };
    let to_ts = match chrono::NaiveDate::parse_from_str(to, "%Y-%m-%d") {
        Ok(d) => d.and_hms_opt(23, 59, 59).unwrap().and_utc(),
        Err(_) => {
            return (StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid to date" }))).into_response();
        }
    };

    let total_revenue: i64 = sqlx::query_scalar::<_, i64>(
        "SELECT COALESCE(SUM(amount_inr), 0) FROM payments WHERE status = 'SUCCESS' AND created_at >= $1 AND created_at <= $2",
    )
    .bind(from_ts)
    .bind(to_ts)
    .fetch_one(&state.pool)
    .await
    .unwrap_or(0);

    let total_orders: i64 = sqlx::query_scalar::<_, i64>(
        "SELECT COUNT(*) FROM payments WHERE status = 'SUCCESS' AND created_at >= $1 AND created_at <= $2",
    )
    .bind(from_ts)
    .bind(to_ts)
    .fetch_one(&state.pool)
    .await
    .unwrap_or(0);

    let total_tracecoins_sold: i64 = sqlx::query_scalar::<_, i64>(
        "SELECT COALESCE(SUM(tracecoins_credited), 0) FROM payments WHERE status = 'SUCCESS' AND created_at >= $1 AND created_at <= $2",
    )
    .bind(from_ts)
    .bind(to_ts)
    .fetch_one(&state.pool)
    .await
    .unwrap_or(0);

    (StatusCode::OK, Json(serde_json::json!({
        "total_revenue": total_revenue,
        "total_orders": total_orders,
        "total_tracecoins_sold": total_tracecoins_sold,
        "from": from,
        "to": to
    }))).into_response()
}
