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

#[derive(Debug, Deserialize)]
pub struct PackageTypeQuery {
    pub package_type: Option<String>,
    pub applicable_role: Option<String>,
    pub role: Option<String>,
    pub active_only: Option<bool>,
}

#[derive(Debug, Deserialize)]
pub struct PaginationQuery {
    pub page: Option<i64>,
    pub limit: Option<i64>,
    pub search: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct CreatePackageRequest {
    pub name: String,
    pub description: Option<String>,
    pub package_type: String,
    pub role_key: Option<String>,
    pub applicable_roles: Option<Vec<String>>,
    pub tracecoins_amount: i32,
    pub price: Option<i32>,
    pub price_inr: Option<i32>,
    pub is_active: Option<bool>,
}

#[derive(Debug, Deserialize)]
pub struct UpdatePackageRequest {
    pub name: Option<String>,
    pub description: Option<String>,
    pub role_key: Option<String>,
    pub applicable_roles: Option<Vec<String>>,
    pub tracecoins_amount: Option<i32>,
    pub price: Option<i32>,
    pub price_inr: Option<i32>,
    pub is_active: Option<bool>,
}

#[derive(Debug, FromRow)]
pub struct PricingPackageRow {
    pub id: Uuid,
    pub name: String,
    pub role_key: String,
    pub package_type: String,
    pub tracecoins_amount: i32,
    pub price_inr: i32,
    pub description: Option<String>,
    pub is_active: bool,
    pub created_at: chrono::DateTime<chrono::Utc>,
}

#[derive(Debug, Serialize)]
pub struct PricingPackageResponse {
    pub id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub role_key: String,
    pub applicable_roles: Vec<String>,
    pub package_type: String,
    pub tracecoins_amount: i32,
    pub price: i32,
    pub price_inr: i32,
    pub duration_days: Option<i32>,
    pub valid_from: Option<chrono::DateTime<chrono::Utc>>,
    pub valid_until: Option<chrono::DateTime<chrono::Utc>>,
    pub is_promotional: bool,
    pub is_active: bool,
    pub features: Option<serde_json::Value>,
    pub created_at: chrono::DateTime<chrono::Utc>,
    pub updated_at: chrono::DateTime<chrono::Utc>,
    pub is_available: bool,
    pub is_expired: bool,
}

impl From<PricingPackageRow> for PricingPackageResponse {
    fn from(row: PricingPackageRow) -> Self {
        Self {
            id: row.id,
            name: row.name,
            description: row.description,
            role_key: row.role_key.clone(),
            applicable_roles: vec![row.role_key],
            package_type: row.package_type,
            tracecoins_amount: row.tracecoins_amount,
            price: row.price_inr,
            price_inr: row.price_inr,
            duration_days: None,
            valid_from: None,
            valid_until: None,
            is_promotional: false,
            is_active: row.is_active,
            features: None,
            created_at: row.created_at,
            updated_at: row.created_at,
            is_available: row.is_active,
            is_expired: false,
        }
    }
}

fn normalize_role_key(role_key: Option<String>, applicable_roles: Option<Vec<String>>) -> Result<String, String> {
    if let Some(role) = role_key {
        let cleaned = role.trim().to_uppercase();
        if !cleaned.is_empty() {
            return Ok(cleaned);
        }
    }

    if let Some(roles) = applicable_roles {
        if let Some(role) = roles.into_iter().map(|role| role.trim().to_uppercase()).find(|role| !role.is_empty()) {
            return Ok(role);
        }
    }

    Err("role_key is required".to_string())
}

fn package_query(base_where: &str, order_by: &str) -> String {
    format!(
        r#"
        SELECT id, name, role_key, package_type, tracecoins_amount, price_inr, description, is_active, created_at
        FROM pricing_packages
        {base_where}
        {order_by}
        "#
    )
}

pub fn router() -> Router<AppState> {
    Router::new()
        .route("/", get(list_packages))
        .route("/", post(create_package))
        .route("/{id}", get(get_package))
        .route("/{id}", patch(update_package))
        .route("/{id}", delete(delete_package))
        .route("/by-type", get(get_packages_by_type))
        .route("/for-role", get(get_packages_for_role))
}

async fn list_packages(
    State(state): State<AppState>,
    Query(q): Query<PaginationQuery>,
) -> impl IntoResponse {
    let page = q.page.unwrap_or(1).max(1);
    let limit = q.limit.unwrap_or(20).clamp(1, 100);
    let offset = (page - 1) * limit;
    let search = q.search.unwrap_or_default().trim().to_string();

    let rows = sqlx::query_as::<_, PricingPackageRow>(
        &format!(
            "{} LIMIT $2 OFFSET $3",
            package_query(
                "WHERE ($1 = '' OR name ILIKE '%' || $1 || '%' OR COALESCE(description, '') ILIKE '%' || $1 || '%')",
                "ORDER BY created_at DESC"
            )
        ),
    )
    .bind(&search)
    .bind(limit)
    .bind(offset)
    .fetch_all(&state.pool)
    .await;

    let rows = match rows {
        Ok(rows) => rows,
        Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    };

    let total: i64 = match sqlx::query_scalar(
        "SELECT COUNT(*) FROM pricing_packages WHERE ($1 = '' OR name ILIKE '%' || $1 || '%' OR COALESCE(description, '') ILIKE '%' || $1 || '%')",
    )
    .bind(&search)
    .fetch_one(&state.pool)
    .await
    {
        Ok(total) => total,
        Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    };

    let packages: Vec<PricingPackageResponse> = rows.into_iter().map(Into::into).collect();

    (StatusCode::OK, Json(serde_json::json!({
        "data": packages,
        "packages": packages,
        "pagination": {
            "page": page,
            "limit": limit,
            "total": total,
            "pages": (total as f64 / limit as f64).ceil() as i64
        }
    })))
        .into_response()
}

async fn get_package(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    match sqlx::query_as::<_, PricingPackageRow>(&package_query("WHERE id = $1", ""))
        .bind(id)
        .fetch_optional(&state.pool)
        .await
    {
        Ok(Some(pkg)) => (StatusCode::OK, Json(PricingPackageResponse::from(pkg))).into_response(),
        Ok(None) => (StatusCode::NOT_FOUND, "Package not found").into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

async fn create_package(
    auth: AuthUser,
    State(state): State<AppState>,
    Json(payload): Json<CreatePackageRequest>,
) -> impl IntoResponse {
    if let Err(e) = require_admin(&auth) {
        return e.into_response();
    }
    let role_key = match normalize_role_key(payload.role_key, payload.applicable_roles) {
        Ok(role_key) => role_key,
        Err(message) => return (StatusCode::BAD_REQUEST, message).into_response(),
    };
    let price_inr = payload.price_inr.or(payload.price).unwrap_or(0);

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

    match result {
        Ok(pkg) => (StatusCode::CREATED, Json(PricingPackageResponse::from(pkg))).into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

async fn update_package(
    auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
    Json(payload): Json<UpdatePackageRequest>,
) -> impl IntoResponse {
    if let Err(e) = require_admin(&auth) {
        return e.into_response();
    }
    let current = match sqlx::query_as::<_, PricingPackageRow>(&package_query("WHERE id = $1", ""))
        .bind(id)
        .fetch_optional(&state.pool)
        .await
    {
        Ok(Some(pkg)) => pkg,
        Ok(None) => return (StatusCode::NOT_FOUND, "Package not found").into_response(),
        Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    };

    let role_key = match normalize_role_key(payload.role_key, payload.applicable_roles) {
        Ok(role_key) => role_key,
        Err(_) => current.role_key.clone(),
    };
    let price_inr = payload.price_inr.or(payload.price).unwrap_or(current.price_inr);

    let updated = sqlx::query_as::<_, PricingPackageRow>(
        r#"
        UPDATE pricing_packages SET
            name = COALESCE($2, name),
            role_key = $3,
            description = COALESCE($4, description),
            tracecoins_amount = COALESCE($5, tracecoins_amount),
            price_inr = $6,
            is_active = COALESCE($7, is_active)
        WHERE id = $1
        RETURNING id, name, role_key, package_type, tracecoins_amount, price_inr, description, is_active, created_at
        "#,
    )
    .bind(id)
    .bind(&payload.name)
    .bind(&role_key)
    .bind(&payload.description)
    .bind(payload.tracecoins_amount)
    .bind(price_inr)
    .bind(payload.is_active)
    .fetch_one(&state.pool)
    .await;

    match updated {
        Ok(pkg) => (StatusCode::OK, Json(PricingPackageResponse::from(pkg))).into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

async fn delete_package(
    auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    if let Err(e) = require_admin(&auth) {
        return e.into_response();
    }
    match sqlx::query("DELETE FROM pricing_packages WHERE id = $1")
        .bind(id)
        .execute(&state.pool)
        .await
    {
        Ok(result) if result.rows_affected() > 0 => {
            (StatusCode::OK, Json(serde_json::json!({"message": "Package deleted"}))).into_response()
        }
        Ok(_) => (StatusCode::NOT_FOUND, "Package not found").into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

async fn get_packages_by_type(
    State(state): State<AppState>,
    Query(q): Query<PackageTypeQuery>,
) -> impl IntoResponse {
    let package_type = q.package_type.unwrap_or_else(|| "TRACECOIN_BUNDLE".to_string());

    let rows = sqlx::query_as::<_, PricingPackageRow>(
        &package_query(
            "WHERE package_type = $1 AND is_active = true",
            "ORDER BY price_inr ASC, created_at DESC",
        ),
    )
    .bind(&package_type)
    .fetch_all(&state.pool)
    .await;

    let rows = match rows {
        Ok(rows) => rows,
        Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    };

    let packages: Vec<PricingPackageResponse> = rows.into_iter().map(Into::into).collect();

    (StatusCode::OK, Json(serde_json::json!({
        "data": packages,
        "package_type": package_type
    })))
        .into_response()
}

async fn get_packages_for_role(
    State(state): State<AppState>,
    Query(q): Query<PackageTypeQuery>,
) -> impl IntoResponse {
    let role = q
        .applicable_role
        .or(q.role)
        .unwrap_or_default()
        .trim()
        .to_uppercase();
    let active_only = q.active_only.unwrap_or(true);

    let rows = sqlx::query_as::<_, PricingPackageRow>(
        &package_query(
            "WHERE ($1 = '' OR role_key = $1) AND ($2 = false OR is_active = true)",
            "ORDER BY price_inr ASC, created_at DESC",
        ),
    )
    .bind(&role)
    .bind(active_only)
    .fetch_all(&state.pool)
    .await;

    let rows = match rows {
        Ok(rows) => rows,
        Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    };

    let packages: Vec<PricingPackageResponse> = rows.into_iter().map(Into::into).collect();

    (StatusCode::OK, Json(serde_json::json!({
        "data": packages,
        "applicable_role": role
    })))
        .into_response()
}
