use axum::{
    Json,
    extract::{Extension, Path, State},
    http::StatusCode,
    response::IntoResponse,
};
use chrono::NaiveDate;
use uuid::Uuid;

use application::wrapup::{
    commands::RequestWrapUpCommand,
    delete as delete_wrapup, generate, get_ready_report, get_wrapup,
    list_wrapups::{self, ListWrapUpsQuery},
};
use domain::errors::DomainError;
use domain::models::wrapup::{WrapUpRecord, WrapUpReport, WrapUpScope, WrapUpStatus};
use domain::value_objects::WrapUpId;

use crate::{
    csrf::CsrfToken,
    errors::ApiError,
    extractors::{AdminApiUser, AuthenticatedUser, OptionalCookieUser},
    render::render_page,
    state::AppState,
};
use api_types::wrapup::{
    GenerateWrapUpRequest, WrapUpGeneratedResponse, WrapUpListResponse, WrapUpStatusResponse,
};

fn record_to_dto(r: &WrapUpRecord) -> WrapUpStatusResponse {
    WrapUpStatusResponse {
        id: r.id.value().to_string(),
        user_id: r.user_id.map(|u| u.to_string()),
        status: format!("{:?}", r.status),
        start_date: r.start_date.to_string(),
        end_date: r.end_date.to_string(),
        created_at: r.created_at.to_string(),
        completed_at: r.completed_at.map(|t| t.to_string()),
        error_message: r.error_message.clone(),
    }
}

#[utoipa::path(
    post, path = "/api/v1/wrapups/generate",
    request_body = GenerateWrapUpRequest,
    responses(
        (status = 200, body = WrapUpGeneratedResponse),
        (status = 400, description = "Invalid date format"),
        (status = 401, description = "Unauthorized"),
        (status = 403, description = "Forbidden — admin only"),
    ),
    security(("bearer_auth" = []))
)]
pub async fn post_generate(
    State(state): State<AppState>,
    _admin: AdminApiUser,
    Json(req): Json<GenerateWrapUpRequest>,
) -> Result<Json<WrapUpGeneratedResponse>, ApiError> {
    let start = NaiveDate::parse_from_str(&req.start_date, "%Y-%m-%d")
        .map_err(|_| DomainError::ValidationError("invalid start_date".into()))?;
    let end = NaiveDate::parse_from_str(&req.end_date, "%Y-%m-%d")
        .map_err(|_| DomainError::ValidationError("invalid end_date".into()))?;
    let user_id = req.user_id;
    let cmd = RequestWrapUpCommand {
        user_id,
        start_date: start,
        end_date: end,
    };
    let id = generate::execute(&state.app_ctx.deps.wrapup.generate, cmd).await?;
    Ok(Json(WrapUpGeneratedResponse {
        id: id.value().to_string(),
    }))
}

#[utoipa::path(
    get, path = "/api/v1/wrapups",
    responses(
        (status = 200, body = WrapUpListResponse),
        (status = 401, description = "Unauthorized"),
    ),
    security(("bearer_auth" = []))
)]
pub async fn get_list(
    State(state): State<AppState>,
    user: AuthenticatedUser,
) -> Result<Json<WrapUpListResponse>, ApiError> {
    let records = list_wrapups::execute(
        &state.app_ctx.deps.wrapup.list_wrapups,
        ListWrapUpsQuery {
            user_id: Some(user.0.value()),
        },
    )
    .await?;
    Ok(Json(WrapUpListResponse {
        items: records.iter().map(record_to_dto).collect(),
    }))
}

#[utoipa::path(
    get, path = "/api/v1/wrapups/{id}",
    params(("id" = Uuid, Path, description = "Wrap-up ID")),
    responses(
        (status = 200, body = WrapUpStatusResponse),
        (status = 401, description = "Unauthorized"),
        (status = 404, description = "Not found"),
    ),
    security(("bearer_auth" = []))
)]
pub async fn get_status(
    State(state): State<AppState>,
    _user: AuthenticatedUser,
    Path(id): Path<Uuid>,
) -> Result<Json<WrapUpStatusResponse>, ApiError> {
    let record = get_wrapup::execute(
        &state.app_ctx.deps.wrapup.get_wrapup,
        WrapUpId::from_uuid(id),
    )
    .await?
    .ok_or_else(|| DomainError::NotFound("wrap-up not found".into()))?;
    Ok(Json(record_to_dto(&record)))
}

#[utoipa::path(
    get, path = "/api/v1/wrapups/{id}/report",
    params(("id" = Uuid, Path, description = "Wrap-up ID")),
    responses(
        (status = 200, description = "Report JSON", content_type = "application/json"),
        (status = 202, description = "Still generating"),
        (status = 401, description = "Unauthorized"),
        (status = 404, description = "Not found"),
    ),
    security(("bearer_auth" = []))
)]
pub async fn get_report(
    State(state): State<AppState>,
    _user: AuthenticatedUser,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    match get_wrapup::execute(
        &state.app_ctx.deps.wrapup.get_wrapup,
        WrapUpId::from_uuid(id),
    )
    .await
    {
        Ok(Some(record)) if record.status == WrapUpStatus::Ready => match record.report {
            Some(ref report) => {
                let json = serde_json::to_string(report).unwrap_or_default();
                (StatusCode::OK, [("content-type", "application/json")], json).into_response()
            }
            None => StatusCode::NOT_FOUND.into_response(),
        },
        Ok(Some(_)) => StatusCode::ACCEPTED.into_response(),
        Ok(None) => StatusCode::NOT_FOUND.into_response(),
        Err(e) => crate::errors::domain_error_response(e),
    }
}

#[utoipa::path(
    delete, path = "/api/v1/wrapups/{id}",
    params(("id" = Uuid, Path, description = "Wrap-up ID")),
    responses(
        (status = 204, description = "Deleted"),
        (status = 401, description = "Unauthorized"),
        (status = 403, description = "Forbidden — admin only"),
        (status = 404, description = "Not found"),
    ),
    security(("bearer_auth" = []))
)]
pub async fn delete_wrapup_handler(
    State(state): State<AppState>,
    _admin: AdminApiUser,
    Path(id): Path<Uuid>,
) -> Result<StatusCode, ApiError> {
    delete_wrapup::execute(
        &state.app_ctx.deps.wrapup.delete_wrapup,
        WrapUpId::from_uuid(id),
    )
    .await?;
    Ok(StatusCode::NO_CONTENT)
}

// ── HTML handlers ───────────────────────────────────────────────────────────

fn format_watch_time(minutes: u32) -> String {
    let h = minutes / 60;
    let m = minutes % 60;
    if h > 0 && m > 0 {
        format!("{}h {}m", h, m)
    } else if h > 0 {
        format!("{}h", h)
    } else {
        format!("{}m", m)
    }
}

fn render_wrapup(
    report: &WrapUpReport,
    year: i32,
    ctx: &api_types::HtmlPageContext,
) -> axum::response::Response {
    let rating_max = report
        .rating_distribution
        .iter()
        .copied()
        .max()
        .unwrap_or(1)
        .max(1);
    let rating_pcts: [f64; 5] =
        std::array::from_fn(|i| report.rating_distribution[i] as f64 / rating_max as f64 * 100.0);
    let genre_max = report
        .top_genres
        .first()
        .map(|g| g.count)
        .unwrap_or(1)
        .max(1);
    let genre_pcts: Vec<f64> = report
        .top_genres
        .iter()
        .take(8)
        .map(|g| g.count as f64 / genre_max as f64 * 100.0)
        .collect();
    let tmpl = template_askama::WrapUpPageTemplate {
        ctx,
        report,
        year_label: year.to_string(),
        watch_time_display: format_watch_time(report.total_watch_time_minutes),
        rating_max,
        genre_max,
        rating_pcts,
        genre_pcts,
    };
    render_page(tmpl)
}

pub async fn get_user_wrapup_html(
    OptionalCookieUser(viewer): OptionalCookieUser,
    State(state): State<AppState>,
    Path((user_id, year)): Path<(Uuid, i32)>,
    Extension(csrf): Extension<CsrfToken>,
) -> impl IntoResponse {
    let scope = WrapUpScope::User(user_id);
    let report =
        match get_ready_report::execute(&state.app_ctx.deps.wrapup.get_ready_report, scope, year)
            .await
        {
            Ok(report) => report,
            Err(DomainError::ValidationError(_)) => return StatusCode::BAD_REQUEST.into_response(),
            Err(_) => return StatusCode::NOT_FOUND.into_response(),
        };

    let ctx = super::helpers::build_page_context(&state, viewer, csrf.0).await;
    render_wrapup(&report, year, &ctx)
}

pub async fn get_global_wrapup_html(
    OptionalCookieUser(viewer): OptionalCookieUser,
    State(state): State<AppState>,
    Path(year): Path<i32>,
    Extension(csrf): Extension<CsrfToken>,
) -> impl IntoResponse {
    let report = match get_ready_report::execute(
        &state.app_ctx.deps.wrapup.get_ready_report,
        WrapUpScope::Global,
        year,
    )
    .await
    {
        Ok(report) => report,
        Err(DomainError::ValidationError(_)) => return StatusCode::BAD_REQUEST.into_response(),
        Err(_) => return StatusCode::NOT_FOUND.into_response(),
    };

    let ctx = super::helpers::build_page_context(&state, viewer, csrf.0).await;
    render_wrapup(&report, year, &ctx)
}
