use axum::{
    Json,
    extract::State,
    http::StatusCode,
    response::{IntoResponse, Response},
};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use std::sync::LazyLock;
use tracing::{error, info, warn};
use tranquil_pds::api::ApiError;
use tranquil_pds::api::proxy_client::{is_ssrf_safe, proxy_client};
use tranquil_pds::auth::{AnyUser, Auth};
use tranquil_pds::state::AppState;
use tranquil_pds::types::{Did, DidRef, Nsid};

static CREATE_REPORT_NSID: LazyLock<Nsid> =
    LazyLock::new(|| "com.atproto.moderation.createReport".parse().unwrap());

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReportReasonType {
    #[serde(rename = "com.atproto.moderation.defs#reasonSpam")]
    Spam,
    #[serde(rename = "com.atproto.moderation.defs#reasonViolation")]
    Violation,
    #[serde(rename = "com.atproto.moderation.defs#reasonMisleading")]
    Misleading,
    #[serde(rename = "com.atproto.moderation.defs#reasonSexual")]
    Sexual,
    #[serde(rename = "com.atproto.moderation.defs#reasonRude")]
    Rude,
    #[serde(rename = "com.atproto.moderation.defs#reasonOther")]
    Other,
    #[serde(rename = "com.atproto.moderation.defs#reasonAppeal")]
    Appeal,
}

impl ReportReasonType {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Spam => "com.atproto.moderation.defs#reasonSpam",
            Self::Violation => "com.atproto.moderation.defs#reasonViolation",
            Self::Misleading => "com.atproto.moderation.defs#reasonMisleading",
            Self::Sexual => "com.atproto.moderation.defs#reasonSexual",
            Self::Rude => "com.atproto.moderation.defs#reasonRude",
            Self::Other => "com.atproto.moderation.defs#reasonOther",
            Self::Appeal => "com.atproto.moderation.defs#reasonAppeal",
        }
    }
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateReportInput {
    pub reason_type: ReportReasonType,
    pub reason: Option<String>,
    pub subject: Value,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateReportOutput {
    pub id: i64,
    pub reason_type: ReportReasonType,
    pub reason: Option<String>,
    pub subject: Value,
    pub reported_by: String,
    pub created_at: String,
}

struct ReportServiceConfig {
    url: String,
    did: Did,
}

fn get_report_service_config() -> Option<ReportServiceConfig> {
    let cfg = tranquil_config::get();
    let url = cfg.moderation.report_service_url.clone()?;
    let did_str = cfg.moderation.report_service_did.as_deref()?;
    if url.is_empty() || did_str.is_empty() {
        return None;
    }
    match did_str.parse::<Did>() {
        Ok(did) => Some(ReportServiceConfig { url, did }),
        Err(_) => {
            warn!(
                report_service_did = did_str,
                "invalid report_service_did, handling reports locally"
            );
            None
        }
    }
}

pub async fn create_report(
    State(state): State<AppState>,
    auth: Auth<AnyUser>,
    Json(input): Json<CreateReportInput>,
) -> Response {
    let did = &auth.did;

    if let Some(config) = get_report_service_config() {
        return proxy_to_report_service(&state, &auth, &config.url, &config.did, &input).await;
    }

    create_report_locally(&state, did, auth.status.is_takendown(), input).await
}

async fn proxy_to_report_service(
    state: &AppState,
    auth_user: &tranquil_pds::auth::AuthenticatedUser,
    service_url: &str,
    service_did: &Did,
    input: &CreateReportInput,
) -> Response {
    if let Err(e) = is_ssrf_safe(service_url) {
        error!("Report service URL failed SSRF check: {:?}", e);
        return ApiError::InternalError(Some("Invalid report service configuration".into()))
            .into_response();
    }

    let key_bytes = match &auth_user.key_bytes {
        Some(kb) => kb.clone(),
        None => match state.repos.user.get_with_key_by_did(&auth_user.did).await {
            Ok(Some(user_with_key)) => {
                match tranquil_pds::config::decrypt_key(
                    &user_with_key.key_bytes,
                    user_with_key.encryption_version,
                ) {
                    Ok(key) => key,
                    Err(e) => {
                        error!(error = ?e, "Failed to decrypt user key for report service auth");
                        return ApiError::AuthenticationFailed(Some(
                            "Failed to get signing key".into(),
                        ))
                        .into_response();
                    }
                }
            }
            Ok(None) => {
                return ApiError::AuthenticationFailed(Some("User has no signing key".into()))
                    .into_response();
            }
            Err(e) => {
                error!(error = ?e, "DB error fetching user key for report");
                return ApiError::AuthenticationFailed(Some("Failed to get signing key".into()))
                    .into_response();
            }
        },
    };

    let service_token = match tranquil_pds::auth::create_service_token(
        &auth_user.did,
        &DidRef::from(service_did),
        Some(&CREATE_REPORT_NSID),
        None,
        &key_bytes,
    ) {
        Ok(t) => t,
        Err(e) => {
            error!("Failed to create service token for report: {:?}", e);
            return ApiError::InternalError(None).into_response();
        }
    };

    let target_url = format!("{}/xrpc/com.atproto.moderation.createReport", service_url);
    info!(
        did = %auth_user.did,
        service_did = %service_did,
        "Proxying createReport to report service"
    );

    let request_body = json!({
        "reasonType": input.reason_type,
        "reason": input.reason,
        "subject": input.subject
    });

    let client = proxy_client();
    let result = client
        .post(&target_url)
        .header("Authorization", format!("Bearer {}", service_token))
        .header("Content-Type", "application/json")
        .json(&request_body)
        .send()
        .await;

    match result {
        Ok(resp) => {
            let status = resp.status();
            let headers = resp.headers().clone();

            let body = match resp.bytes().await {
                Ok(b) => b,
                Err(e) => {
                    error!("Error reading report service response: {:?}", e);
                    return (StatusCode::BAD_GATEWAY, "Error reading upstream response")
                        .into_response();
                }
            };

            let mut response_builder = Response::builder().status(status);

            if let Some(ct) = headers.get("content-type") {
                response_builder = response_builder.header("content-type", ct);
            }

            match response_builder.body(axum::body::Body::from(body)) {
                Ok(r) => r,
                Err(e) => {
                    error!("Error building proxy response: {:?}", e);
                    (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error").into_response()
                }
            }
        }
        Err(e) => {
            error!("Error sending report to service: {:?}", e);
            if e.is_timeout() {
                (StatusCode::GATEWAY_TIMEOUT, "Report service timeout").into_response()
            } else {
                (StatusCode::BAD_GATEWAY, "Report service error").into_response()
            }
        }
    }
}

async fn create_report_locally(
    state: &AppState,
    did: &tranquil_pds::types::Did,
    is_takendown: bool,
    input: CreateReportInput,
) -> Response {
    if is_takendown && input.reason_type != ReportReasonType::Appeal {
        return ApiError::InvalidRequest("Report not accepted from takendown account".into())
            .into_response();
    }

    let created_at = chrono::Utc::now();
    let report_id = i64::try_from(uuid::Uuid::now_v7().as_u128() & 0x7FFF_FFFF_FFFF_FFFF)
        .expect("masked to 63 bits, always fits i64");
    let subject_json = json!(input.subject);

    if let Err(e) = state
        .repos
        .infra
        .insert_report(
            report_id,
            input.reason_type.as_str(),
            input.reason.as_deref(),
            subject_json,
            did,
            created_at,
        )
        .await
    {
        error!("Failed to insert report: {:?}", e);
        return ApiError::InternalError(None).into_response();
    }

    info!(
        report_id = %report_id,
        reported_by = %did,
        reason_type = input.reason_type.as_str(),
        "Report created locally (no report service configured)"
    );

    (
        StatusCode::OK,
        Json(CreateReportOutput {
            id: report_id,
            reason_type: input.reason_type,
            reason: input.reason,
            subject: input.subject,
            reported_by: did.to_string(),
            created_at: created_at.to_rfc3339(),
        }),
    )
        .into_response()
}
