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

// ── Router ────────────────────────────────────────────────────────────────────

pub fn admin_router() -> Router<AppState> {
    Router::new()
        .route("/", get(admin_list_reviews).post(admin_create_review))
        .route("/{id}", axum::routing::patch(admin_update_review).delete(admin_delete_review))
}

pub fn public_router() -> Router<AppState> {
    Router::new()
        .route("/", get(list_reviews))
        .route("/professional/{professional_id}", get(list_reviews_by_professional))
}

// ── DTOs ──────────────────────────────────────────────────────────────────────

#[derive(Serialize)]
struct ReviewDto {
    id: Uuid,
    professional_id: Uuid,
    customer_id: Uuid,
    rating: i16,
    comment: Option<String>,
    is_published: bool,
    created_at: chrono::DateTime<chrono::Utc>,
}

#[derive(Serialize)]
struct PublicReviewDto {
    id: Uuid,
    professional_id: Uuid,
    rating: i16,
    comment: Option<String>,
    created_at: String,
}

#[derive(Deserialize)]
struct CreateReviewBody {
    #[allow(dead_code)]
    lead_request_id: Uuid,
    rating: i16,
    comment: Option<String>,
}

#[derive(Deserialize)]
struct PatchReviewBody {
    is_published: Option<bool>,
}

#[derive(Deserialize)]
struct PublicListQuery {
    page: Option<i64>,
    limit: Option<i64>,
}

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

#[derive(sqlx::FromRow)]
struct ReviewRow {
    id: Uuid,
    #[allow(dead_code)]
    lead_request_id: Uuid,
    customer_id: Uuid,
    professional_id: Uuid,
    rating: i16,
    comment: Option<String>,
    is_published: bool,
    created_at: chrono::DateTime<chrono::Utc>,
}

// ── Handlers ──────────────────────────────────────────────────────────────────

async fn admin_list_reviews(
    _auth: AuthUser,
    State(state): State<AppState>,
) -> impl IntoResponse {
    let rows = sqlx::query_as::<_, ReviewRow>(
        r#"
        SELECT
            r.id,
            r.lead_request_id,
            r.customer_id,
            r.professional_id,
            r.rating,
            r.comment,
            r.is_published,
            r.created_at
        FROM reviews r
        ORDER BY r.created_at DESC
        "#,
    )
    .fetch_all(&state.pool)
    .await;

    match rows {
        Ok(rows) => {
            let dtos: Vec<ReviewDto> = rows
                .into_iter()
                .map(|r| ReviewDto {
                    id: r.id,
                    professional_id: r.professional_id,
                    customer_id: r.customer_id,
                    rating: r.rating,
                    comment: r.comment,
                    is_published: r.is_published,
                    created_at: r.created_at,
                })
                .collect();
            (StatusCode::OK, Json(serde_json::json!({ "reviews": dtos }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to list reviews: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to load reviews" }))).into_response()
        }
    }
}

async fn admin_create_review(
    _auth: AuthUser,
    State(state): State<AppState>,
    Json(body): Json<CreateReviewBody>,
) -> impl IntoResponse {
    if body.rating < 1 || body.rating > 5 {
        return (StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Rating must be 1-5" }))).into_response();
    }

    let row = sqlx::query_as::<_, ReviewRow>(
        r#"
        INSERT INTO reviews (lead_request_id, customer_id, professional_id, rating, comment, is_published)
        SELECT $1,
               (SELECT id FROM customer_profiles WHERE user_id = (SELECT customer_user_id FROM lead_requests WHERE id = $1)),
               (SELECT user_role_profile_id FROM lead_requests WHERE id = $1),
               $2, $3, true
        RETURNING id, lead_request_id, customer_id, professional_id, rating, comment, is_published, created_at
        "#,
    )
    .bind(body.lead_request_id)
    .bind(body.rating)
    .bind(&body.comment)
    .fetch_one(&state.pool)
    .await;

    match row {
        Ok(r) => {
            let dto = ReviewDto {
                id: r.id,
                professional_id: r.professional_id,
                customer_id: r.customer_id,
                rating: r.rating,
                comment: r.comment,
                is_published: r.is_published,
                created_at: r.created_at,
            };
            (StatusCode::CREATED, Json(serde_json::json!(dto))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to create review: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to create review" }))).into_response()
        }
    }
}

async fn admin_update_review(
    _auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
    Json(body): Json<PatchReviewBody>,
) -> impl IntoResponse {
    let is_published = body.is_published.unwrap_or(true);

    let result = sqlx::query(
        "UPDATE reviews SET is_published = $1, updated_at = NOW() WHERE id = $2",
    )
    .bind(is_published)
    .bind(id)
    .execute(&state.pool)
    .await;

    match result {
        Ok(r) if r.rows_affected() == 0 => {
            (StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "Review not found" }))).into_response()
        }
        Ok(_) => (StatusCode::OK, Json(serde_json::json!({ "ok": true }))).into_response(),
        Err(e) => {
            tracing::error!("Failed to update review {id}: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to update review" }))).into_response()
        }
    }
}

async fn admin_delete_review(
    _auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    let result = sqlx::query("DELETE FROM reviews 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": "Review not found" }))).into_response()
        }
        Ok(_) => (StatusCode::NO_CONTENT).into_response(),
        Err(e) => {
            tracing::error!("Failed to delete review {id}: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to delete review" }))).into_response()
        }
    }
}

// ── Public handlers ────────────────────────────────────────────────────────────

async fn list_reviews(
    State(state): State<AppState>,
    Query(q): Query<PublicListQuery>,
) -> 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 rows = sqlx::query_as::<_, ReviewRow>(
        r#"
        SELECT id, lead_request_id, customer_id, professional_id, rating, comment, is_published, created_at
        FROM reviews
        WHERE is_published = true
        ORDER BY created_at DESC
        LIMIT $1 OFFSET $2
        "#,
    )
    .bind(limit)
    .bind(offset)
    .fetch_all(&state.pool)
    .await;

    match rows {
        Ok(rows) => {
            let dtos: Vec<PublicReviewDto> = rows
                .into_iter()
                .map(|r| PublicReviewDto {
                    id: r.id,
                    professional_id: r.professional_id,
                    rating: r.rating,
                    comment: r.comment,
                    created_at: r.created_at.to_rfc3339(),
                })
                .collect();
            (StatusCode::OK, Json(serde_json::json!({ "reviews": dtos }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to list public reviews: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to load reviews" }))).into_response()
        }
    }
}

async fn list_reviews_by_professional(
    State(state): State<AppState>,
    Path(professional_id): Path<Uuid>,
    Query(q): Query<PublicListQuery>,
) -> 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 rows = sqlx::query_as::<_, ReviewRow>(
        r#"
        SELECT id, lead_request_id, customer_id, professional_id, rating, comment, is_published, created_at
        FROM reviews
        WHERE professional_id = $1 AND is_published = true
        ORDER BY created_at DESC
        LIMIT $2 OFFSET $3
        "#,
    )
    .bind(professional_id)
    .bind(limit)
    .bind(offset)
    .fetch_all(&state.pool)
    .await;

    match rows {
        Ok(rows) => {
            let avg: (f64,) = sqlx::query_as("SELECT COALESCE(AVG(rating), 0)::float FROM reviews WHERE professional_id = $1 AND is_published = true")
                .bind(professional_id)
                .fetch_one(&state.pool)
                .await
                .unwrap_or((0.0,));
            let count: (i64,) = sqlx::query_scalar("SELECT COUNT(*) FROM reviews WHERE professional_id = $1 AND is_published = true")
                .bind(professional_id)
                .fetch_one(&state.pool)
                .await
                .unwrap_or((0,));
            let dtos: Vec<PublicReviewDto> = rows
                .into_iter()
                .map(|r| PublicReviewDto {
                    id: r.id,
                    professional_id: r.professional_id,
                    rating: r.rating,
                    comment: r.comment,
                    created_at: r.created_at.to_rfc3339(),
                })
                .collect();
            (StatusCode::OK, Json(serde_json::json!({
                "reviews": dtos,
                "averageRating": avg.0,
                "totalCount": count.0
            }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to list reviews for professional {professional_id}: {e}");
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to load reviews" }))).into_response()
        }
    }
}
