use askama::Template;
use askama_web::WebTemplate;
use axum::extract::{Path, Query, State};
use axum::http::HeaderMap;
use axum::response::{AppendHeaders, IntoResponse, Redirect, Response};
use axum::Form;
use serde::Deserialize;
use sqlx::FromRow;

use crate::auth_guard::AdminUser;
use crate::flash;
use crate::page::PageContext;
use crate::state::AppState;

async fn audit(
    db: &sqlx::PgPool,
    admin_sub: &str,
    action: &str,
    target_type: &str,
    target_id: &str,
    metadata: Option<serde_json::Value>,
) {
    if let Err(e) = sqlx::query(
        "INSERT INTO admin_audit_log (admin_sub, action, target_type, target_id, metadata)
         VALUES ($1, $2, $3, $4, $5)",
    )
    .bind(admin_sub)
    .bind(action)
    .bind(target_type)
    .bind(target_id)
    .bind(metadata)
    .execute(db)
    .await
    {
        tracing::error!("audit log insert failed: {e}");
    }
}

async fn set_keycloak_user_enabled(state: &AppState, sub: &str, enabled: bool) -> Result<(), String> {
    let token = crate::keycloak::get_admin_token(state).await?;

    let base_url = state.oidc.issuer_url.replace("/realms/irc-now", "");
    let url = format!("{}/admin/realms/irc-now/users/{}", base_url, sub);

    let resp = state
        .http_client
        .put(&url)
        .bearer_auth(&token)
        .json(&serde_json::json!({ "enabled": enabled }))
        .send()
        .await
        .map_err(|e| format!("keycloak user update failed: {e}"))?;

    if !resp.status().is_success() {
        let status = resp.status();
        let body = resp.text().await.unwrap_or_default();
        return Err(format!("keycloak user update returned {status}: {body}"));
    }

    Ok(())
}

#[derive(FromRow)]
#[allow(dead_code)]
struct ReportRow {
    id: i64,
    content_type: String,
    content_id: String,
    content_url: String,
    reporter_email: Option<String>,
    reason: String,
    description: Option<String>,
    status: String,
    resolution: Option<String>,
    resolved_by: Option<String>,
    resolved_at: Option<chrono::DateTime<chrono::Utc>>,
    created_at: chrono::DateTime<chrono::Utc>,
}

#[allow(dead_code)]
pub struct ReportEntry {
    pub id: i64,
    pub content_type: String,
    pub reason: String,
    pub status: String,
    pub created_at: String,
    pub content_url: String,
}

#[derive(Template, WebTemplate)]
#[template(path = "admin_reports.html")]
pub struct AdminReportsTemplate {
    pub page: PageContext,
    pub is_admin: bool,
    pub reports: Vec<ReportEntry>,
    pub filter: String,
}

#[derive(Deserialize)]
pub struct ReportsQuery {
    pub status: Option<String>,
}

pub async fn reports(
    headers: HeaderMap,
    State(state): State<AppState>,
    AdminUser(_admin): AdminUser,
    Query(q): Query<ReportsQuery>,
) -> Result<Response, axum::http::StatusCode> {
    let page = PageContext::from_request(&headers, &state.announcement);
    let clear = page.flash.is_some();
    let filter = q.status.unwrap_or_else(|| "pending".to_string());

    let rows = sqlx::query_as::<_, ReportRow>(
        "SELECT * FROM abuse_reports WHERE status = $1 ORDER BY created_at DESC LIMIT 100",
    )
    .bind(&filter)
    .fetch_all(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("reports query failed: {e}");
        axum::http::StatusCode::INTERNAL_SERVER_ERROR
    })?;

    let reports = rows
        .into_iter()
        .map(|r| ReportEntry {
            id: r.id,
            content_type: r.content_type,
            reason: r.reason,
            status: r.status,
            created_at: r.created_at.format("%Y-%m-%d %H:%M").to_string(),
            content_url: r.content_url,
        })
        .collect();

    let template = AdminReportsTemplate {
        page,
        is_admin: true,
        reports,
        filter,
    };

    if clear {
        Ok((AppendHeaders([flash::clear_flash()]), template).into_response())
    } else {
        Ok(template.into_response())
    }
}

#[derive(Template, WebTemplate)]
#[template(path = "admin_report_detail.html")]
pub struct AdminReportDetailTemplate {
    pub page: PageContext,
    pub is_admin: bool,
    pub report: ReportDetailView,
}

pub struct ReportDetailView {
    pub id: i64,
    pub content_type: String,
    pub content_id: String,
    pub content_url: String,
    pub reporter_email: String,
    pub reason: String,
    pub description: String,
    pub status: String,
    pub resolution: String,
    pub created_at: String,
}

pub async fn report_detail(
    headers: HeaderMap,
    State(state): State<AppState>,
    AdminUser(_admin): AdminUser,
    Path(id): Path<i64>,
) -> Result<AdminReportDetailTemplate, axum::http::StatusCode> {
    let page = PageContext::from_request(&headers, &state.announcement);

    let row = sqlx::query_as::<_, ReportRow>(
        "SELECT * FROM abuse_reports WHERE id = $1",
    )
    .bind(id)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("report detail query failed: {e}");
        axum::http::StatusCode::INTERNAL_SERVER_ERROR
    })?
    .ok_or(axum::http::StatusCode::NOT_FOUND)?;

    Ok(AdminReportDetailTemplate {
        page,
        is_admin: true,
        report: ReportDetailView {
            id: row.id,
            content_type: row.content_type,
            content_id: row.content_id,
            content_url: row.content_url,
            reporter_email: row.reporter_email.unwrap_or_default(),
            reason: row.reason,
            description: row.description.unwrap_or_default(),
            status: row.status,
            resolution: row.resolution.unwrap_or_default(),
            created_at: row.created_at.format("%Y-%m-%d %H:%M UTC").to_string(),
        },
    })
}

#[derive(Deserialize)]
pub struct ReportActionForm {
    action: String,
}

pub async fn report_action(
    State(state): State<AppState>,
    AdminUser(admin): AdminUser,
    Path(id): Path<i64>,
    Form(form): Form<ReportActionForm>,
) -> Response {
    let row = match sqlx::query_as::<_, ReportRow>(
        "SELECT * FROM abuse_reports WHERE id = $1",
    )
    .bind(id)
    .fetch_optional(&state.db)
    .await
    {
        Ok(Some(r)) => r,
        Ok(None) => return axum::http::StatusCode::NOT_FOUND.into_response(),
        Err(e) => {
            tracing::error!("report query failed: {e}");
            return axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response();
        }
    };

    match form.action.as_str() {
        "dismiss" => {
            let _ = sqlx::query(
                "UPDATE abuse_reports SET status = 'dismissed', resolution = 'dismissed', resolved_by = $1, resolved_at = NOW() WHERE id = $2",
            )
            .bind(&admin.sub)
            .bind(id)
            .execute(&state.db)
            .await;

            audit(&state.db, &admin.sub, "dismiss_report", "report", &id.to_string(), None).await;
        }
        "hide" => {
            let internal_url = match row.content_type.as_str() {
                "image" => format!("{}/api/internal/images/{}/hide", state.pics_url, row.content_id),
                "paste" => format!("{}/api/internal/pastes/{}/hide", state.txt_url, row.content_id),
                _ => String::new(),
            };

            if !internal_url.is_empty() {
                let _ = state.http_client.post(&internal_url).send().await;
            }

            let _ = sqlx::query(
                "UPDATE abuse_reports SET status = 'resolved', resolution = 'content_hidden', resolved_by = $1, resolved_at = NOW() WHERE id = $2",
            )
            .bind(&admin.sub)
            .bind(id)
            .execute(&state.db)
            .await;

            audit(&state.db, &admin.sub, "hide_content", &row.content_type, &row.content_id, None).await;
        }
        "delete" => {
            let internal_url = match row.content_type.as_str() {
                "image" => format!("{}/api/internal/images/{}", state.pics_url, row.content_id),
                "paste" => format!("{}/api/internal/pastes/{}", state.txt_url, row.content_id),
                _ => String::new(),
            };

            if !internal_url.is_empty() {
                let _ = state.http_client.delete(&internal_url).send().await;
            }

            let _ = sqlx::query(
                "UPDATE abuse_reports SET status = 'resolved', resolution = 'content_deleted', resolved_by = $1, resolved_at = NOW() WHERE id = $2",
            )
            .bind(&admin.sub)
            .bind(id)
            .execute(&state.db)
            .await;

            audit(&state.db, &admin.sub, "delete_content", &row.content_type, &row.content_id, None).await;
        }
        "suspend" => {
            let _owner_sub: Option<String> = match row.content_type.as_str() {
                "image" => {
                    sqlx::query_scalar("SELECT user_id FROM images WHERE id = $1")
                        .bind(&row.content_id)
                        .fetch_optional(&state.db)
                        .await
                        .ok()
                        .flatten()
                }
                "paste" => {
                    sqlx::query_scalar("SELECT user_id FROM pastes WHERE id = $1")
                        .bind(&row.content_id)
                        .fetch_optional(&state.db)
                        .await
                        .ok()
                        .flatten()
                }
                _ => None,
            };

            // Note: user_id queries above go to accounts-db, but images/pastes are in separate DBs.
            // The admin panel calls the internal APIs which handle their own DB.
            // For suspension we just need the user sub which is stored in the report or looked up.
            // Since we can't query pics/txt DBs from web-api, we skip the auto-lookup
            // and the admin can suspend from the user detail page instead.

            let _ = sqlx::query(
                "UPDATE abuse_reports SET status = 'resolved', resolution = 'user_suspended', resolved_by = $1, resolved_at = NOW() WHERE id = $2",
            )
            .bind(&admin.sub)
            .bind(id)
            .execute(&state.db)
            .await;

            audit(&state.db, &admin.sub, "suspend_from_report", "report", &id.to_string(), None).await;
        }
        _ => {}
    }

    (
        AppendHeaders([flash::set_flash("report_actioned")]),
        Redirect::to("/admin/reports"),
    )
        .into_response()
}

#[derive(FromRow)]
struct UserSearchRow {
    keycloak_sub: String,
    email: Option<String>,
    plan: String,
    is_admin: bool,
    suspended_at: Option<chrono::DateTime<chrono::Utc>>,
    created_at: Option<chrono::DateTime<chrono::Utc>>,
}

#[allow(dead_code)]
pub struct UserSearchEntry {
    pub sub: String,
    pub email: String,
    pub plan: String,
    pub is_admin: bool,
    pub suspended: bool,
    pub created_at: String,
}

#[derive(Template, WebTemplate)]
#[template(path = "admin_users.html")]
pub struct AdminUsersTemplate {
    pub page: PageContext,
    pub is_admin: bool,
    pub users: Vec<UserSearchEntry>,
    pub query: String,
}

#[derive(Deserialize)]
pub struct UsersQuery {
    pub q: Option<String>,
}

pub async fn users(
    headers: HeaderMap,
    State(state): State<AppState>,
    AdminUser(_admin): AdminUser,
    Query(q): Query<UsersQuery>,
) -> Result<AdminUsersTemplate, axum::http::StatusCode> {
    let page = PageContext::from_request(&headers, &state.announcement);
    let query = q.q.unwrap_or_default();

    let rows = if query.is_empty() {
        sqlx::query_as::<_, UserSearchRow>(
            "SELECT keycloak_sub, email, plan, is_admin, suspended_at, created_at
             FROM users ORDER BY created_at DESC LIMIT 50",
        )
        .fetch_all(&state.db)
        .await
    } else {
        sqlx::query_as::<_, UserSearchRow>(
            "SELECT keycloak_sub, email, plan, is_admin, suspended_at, created_at
             FROM users WHERE email ILIKE $1 OR keycloak_sub = $2
             ORDER BY created_at DESC LIMIT 50",
        )
        .bind(format!("%{query}%"))
        .bind(&query)
        .fetch_all(&state.db)
        .await
    };

    let rows = rows.map_err(|e| {
        tracing::error!("users query failed: {e}");
        axum::http::StatusCode::INTERNAL_SERVER_ERROR
    })?;

    let users = rows
        .into_iter()
        .map(|r| UserSearchEntry {
            sub: r.keycloak_sub,
            email: r.email.unwrap_or_default(),
            plan: r.plan,
            is_admin: r.is_admin,
            suspended: r.suspended_at.is_some(),
            created_at: r
                .created_at
                .map(|t| t.format("%Y-%m-%d").to_string())
                .unwrap_or_default(),
        })
        .collect();

    Ok(AdminUsersTemplate { page, users, query })
}

#[derive(FromRow)]
struct UserDetailRow {
    keycloak_sub: String,
    email: Option<String>,
    plan: String,
    display_name: Option<String>,
    is_admin: bool,
    suspended_at: Option<chrono::DateTime<chrono::Utc>>,
    suspended_reason: Option<String>,
    created_at: Option<chrono::DateTime<chrono::Utc>>,
}

#[derive(Template, WebTemplate)]
#[template(path = "admin_user_detail.html")]
pub struct AdminUserDetailTemplate {
    pub page: PageContext,
    pub user: UserDetailView,
    pub report_count: i64,
}

pub struct UserDetailView {
    pub sub: String,
    pub email: String,
    pub display_name: String,
    pub plan: String,
    pub is_admin: bool,
    pub suspended: bool,
    pub suspended_reason: String,
    pub created_at: String,
}

pub async fn user_detail(
    headers: HeaderMap,
    State(state): State<AppState>,
    AdminUser(_admin): AdminUser,
    Path(sub): Path<String>,
) -> Result<Response, axum::http::StatusCode> {
    let page = PageContext::from_request(&headers, &state.announcement);
    let clear = page.flash.is_some();

    let row = sqlx::query_as::<_, UserDetailRow>(
        "SELECT keycloak_sub, email, plan, display_name, is_admin, suspended_at, suspended_reason, created_at
         FROM users WHERE keycloak_sub = $1",
    )
    .bind(&sub)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("user detail query failed: {e}");
        axum::http::StatusCode::INTERNAL_SERVER_ERROR
    })?
    .ok_or(axum::http::StatusCode::NOT_FOUND)?;

    let report_count: (i64,) = sqlx::query_as(
        "SELECT COUNT(*) FROM abuse_reports WHERE reporter_sub = $1",
    )
    .bind(&sub)
    .fetch_one(&state.db)
    .await
    .unwrap_or((0,));

    let template = AdminUserDetailTemplate {
        page,
        user: UserDetailView {
            sub: row.keycloak_sub,
            email: row.email.unwrap_or_default(),
            display_name: row.display_name.unwrap_or_default(),
            plan: row.plan,
            is_admin: row.is_admin,
            suspended: row.suspended_at.is_some(),
            suspended_reason: row.suspended_reason.unwrap_or_default(),
            created_at: row
                .created_at
                .map(|t| t.format("%Y-%m-%d").to_string())
                .unwrap_or_default(),
        },
        report_count: report_count.0,
    };

    if clear {
        Ok((AppendHeaders([flash::clear_flash()]), template).into_response())
    } else {
        Ok(template.into_response())
    }
}

#[derive(Deserialize)]
pub struct SuspendForm {
    reason: Option<String>,
}

pub async fn suspend_user(
    State(state): State<AppState>,
    AdminUser(admin): AdminUser,
    Path(sub): Path<String>,
    Form(form): Form<SuspendForm>,
) -> Response {
    let reason = form.reason.filter(|r| !r.trim().is_empty());

    if let Err(e) = set_keycloak_user_enabled(&state, &sub, false).await {
        tracing::error!("keycloak disable failed for {sub}: {e}");
        return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, format!("keycloak disable failed: {e}")).into_response();
    }

    let _ = sqlx::query(
        "UPDATE users SET suspended_at = NOW(), suspended_reason = $1 WHERE keycloak_sub = $2",
    )
    .bind(&reason)
    .bind(&sub)
    .execute(&state.db)
    .await;

    audit(
        &state.db,
        &admin.sub,
        "suspend_user",
        "user",
        &sub,
        reason.as_ref().map(|r| serde_json::json!({ "reason": r })),
    )
    .await;

    (
        AppendHeaders([flash::set_flash("user_suspended")]),
        Redirect::to(&format!("/admin/users/{sub}")),
    )
        .into_response()
}

pub async fn unsuspend_user(
    State(state): State<AppState>,
    AdminUser(admin): AdminUser,
    Path(sub): Path<String>,
) -> Response {
    if let Err(e) = set_keycloak_user_enabled(&state, &sub, true).await {
        tracing::error!("keycloak enable failed: {e}");
    }

    let _ = sqlx::query(
        "UPDATE users SET suspended_at = NULL, suspended_reason = NULL WHERE keycloak_sub = $1",
    )
    .bind(&sub)
    .execute(&state.db)
    .await;

    audit(&state.db, &admin.sub, "unsuspend_user", "user", &sub, None).await;

    (
        AppendHeaders([flash::set_flash("user_unsuspended")]),
        Redirect::to(&format!("/admin/users/{sub}")),
    )
        .into_response()
}

#[derive(FromRow)]
struct AuditRow {
    admin_sub: String,
    action: String,
    target_type: String,
    target_id: String,
    created_at: chrono::DateTime<chrono::Utc>,
}

pub struct AuditEntry {
    pub admin_sub: String,
    pub action: String,
    pub target_type: String,
    pub target_id: String,
    pub created_at: String,
}

#[derive(Template, WebTemplate)]
#[template(path = "admin_audit.html")]
pub struct AdminAuditTemplate {
    pub page: PageContext,
    pub entries: Vec<AuditEntry>,
}

pub async fn audit_log(
    headers: HeaderMap,
    State(state): State<AppState>,
    AdminUser(_admin): AdminUser,
) -> Result<AdminAuditTemplate, axum::http::StatusCode> {
    let page = PageContext::from_request(&headers, &state.announcement);

    let rows = sqlx::query_as::<_, AuditRow>(
        "SELECT admin_sub, action, target_type, target_id, created_at
         FROM admin_audit_log ORDER BY created_at DESC LIMIT 100",
    )
    .fetch_all(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("audit log query failed: {e}");
        axum::http::StatusCode::INTERNAL_SERVER_ERROR
    })?;

    let entries = rows
        .into_iter()
        .map(|r| AuditEntry {
            admin_sub: r.admin_sub,
            action: r.action,
            target_type: r.target_type,
            target_id: r.target_id,
            created_at: r.created_at.format("%Y-%m-%d %H:%M UTC").to_string(),
        })
        .collect();

    Ok(AdminAuditTemplate { page, entries })
}

#[derive(FromRow)]
struct StatsRow {
    label: String,
    count: i64,
}

#[derive(Template, WebTemplate)]
#[template(path = "admin_stats.html")]
pub struct AdminStatsTemplate {
    pub page: PageContext,
    pub total_reports: i64,
    pub pending_reports: i64,
    pub resolved_reports: i64,
    pub dismissed_reports: i64,
    pub by_reason: Vec<StatEntry>,
}

pub struct StatEntry {
    pub label: String,
    pub count: i64,
}

pub async fn stats(
    headers: HeaderMap,
    State(state): State<AppState>,
    AdminUser(_admin): AdminUser,
) -> Result<AdminStatsTemplate, axum::http::StatusCode> {
    let page = PageContext::from_request(&headers, &state.announcement);

    let total: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM abuse_reports")
        .fetch_one(&state.db)
        .await
        .unwrap_or((0,));
    let pending: (i64,) =
        sqlx::query_as("SELECT COUNT(*) FROM abuse_reports WHERE status = 'pending'")
            .fetch_one(&state.db)
            .await
            .unwrap_or((0,));
    let resolved: (i64,) =
        sqlx::query_as("SELECT COUNT(*) FROM abuse_reports WHERE status = 'resolved'")
            .fetch_one(&state.db)
            .await
            .unwrap_or((0,));
    let dismissed: (i64,) =
        sqlx::query_as("SELECT COUNT(*) FROM abuse_reports WHERE status = 'dismissed'")
            .fetch_one(&state.db)
            .await
            .unwrap_or((0,));

    let reason_rows = sqlx::query_as::<_, StatsRow>(
        "SELECT reason AS label, COUNT(*) AS count FROM abuse_reports GROUP BY reason ORDER BY count DESC",
    )
    .fetch_all(&state.db)
    .await
    .unwrap_or_default();

    let by_reason = reason_rows
        .into_iter()
        .map(|r| StatEntry {
            label: r.label,
            count: r.count,
        })
        .collect();

    Ok(AdminStatsTemplate {
        page,
        total_reports: total.0,
        pending_reports: pending.0,
        resolved_reports: resolved.0,
        dismissed_reports: dismissed.0,
        by_reason,
    })
}
