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

pub fn persona_types_router() -> Router<AppState> {
    Router::new()
        .route("/api/admin/persona-types", get(list_persona_types))
}

pub fn modules_router() -> Router<AppState> {
    Router::new()
        .route("/api/admin/modules", get(list_modules))
}

pub fn role_modules_router() -> Router<AppState> {
    Router::new()
        .route("/api/admin/roles/{id}/modules", get(get_role_modules).post(add_role_module))
        .route("/api/admin/roles/{id}/modules/{module_id}", axum::routing::delete(remove_role_module))
        .route("/api/admin/roles/{id}/permissions", get(get_role_permissions).put(update_role_permission))
}

#[derive(Serialize, sqlx::FromRow)]
struct PersonaTypeRow {
    id: Uuid,
    code: String,
    name: String,
    description: Option<String>,
    is_active: bool,
}

async fn list_persona_types(
    _auth: AuthUser,
    State(state): State<AppState>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let rows = sqlx::query_as::<_, PersonaTypeRow>(
        "SELECT id, code, name, description, is_active FROM persona_types WHERE is_active = true ORDER BY name",
    )
    .fetch_all(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    Ok(Json(rows))
}

#[derive(Serialize, sqlx::FromRow)]
struct ModuleRow {
    id: Uuid,
    module_key: String,
    module_name: String,
    category: String,
    description: Option<String>,
    backend_domain: Option<String>,
    default_route: Option<String>,
    default_sidebar_label: Option<String>,
    icon_key: Option<String>,
    is_core: bool,
    is_active: bool,
}

async fn list_modules(
    auth: AuthUser,
    State(state): State<AppState>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;

    let rows = sqlx::query_as::<_, ModuleRow>(
        r#"
        SELECT id, module_key, module_name, category, description,
               backend_domain, default_route, default_sidebar_label,
               icon_key, is_core, is_active
        FROM modules
        WHERE is_active = true
        ORDER BY category, module_name
        "#,
    )
    .fetch_all(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    Ok(Json(rows))
}

#[derive(Serialize, sqlx::FromRow)]
struct RoleModuleAccessRow {
    id: Uuid,
    module_id: Uuid,
    module_key: String,
    module_name: String,
    is_enabled: bool,
    is_sidebar_visible: bool,
    sidebar_label_override: Option<String>,
    route_override: Option<String>,
}

async fn get_role_modules(
    auth: AuthUser,
    State(state): State<AppState>,
    Path(role_id): Path<Uuid>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;

    let rows = sqlx::query_as::<_, RoleModuleAccessRow>(
        r#"
        SELECT rma.id, rma.module_id, m.module_key, m.module_name,
               rma.is_enabled, rma.is_sidebar_visible,
               rma.sidebar_label_override, rma.route_override
        FROM role_module_access rma
        JOIN modules m ON m.id = rma.module_id
        WHERE rma.role_id = $1
        ORDER BY m.category, m.module_name
        "#,
    )
    .bind(role_id)
    .fetch_all(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    Ok(Json(rows))
}

#[derive(Deserialize)]
struct AddModulePayload {
    module_id: Uuid,
    is_enabled: Option<bool>,
    is_sidebar_visible: Option<bool>,
    sidebar_label_override: Option<String>,
    route_override: Option<String>,
}

async fn add_role_module(
    auth: AuthUser,
    State(state): State<AppState>,
    Path(role_id): Path<Uuid>,
    Json(payload): Json<AddModulePayload>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;

    let is_enabled = payload.is_enabled.unwrap_or(true);
    let is_sidebar_visible = payload.is_sidebar_visible.unwrap_or(true);

    sqlx::query(
        r#"
        INSERT INTO role_module_access (role_id, module_id, is_enabled, is_sidebar_visible, sidebar_label_override, route_override)
        VALUES ($1, $2, $3, $4, $5, $6)
        ON CONFLICT (role_id, module_id) DO UPDATE SET
            is_enabled = EXCLUDED.is_enabled,
            is_sidebar_visible = EXCLUDED.is_sidebar_visible,
            sidebar_label_override = EXCLUDED.sidebar_label_override,
            route_override = EXCLUDED.route_override
        "#,
    )
    .bind(role_id)
    .bind(payload.module_id)
    .bind(is_enabled)
    .bind(is_sidebar_visible)
    .bind(&payload.sidebar_label_override)
    .bind(&payload.route_override)
    .execute(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    Ok(StatusCode::CREATED)
}

async fn remove_role_module(
    auth: AuthUser,
    State(state): State<AppState>,
    Path((role_id, module_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;

    let result = sqlx::query(
        "DELETE FROM role_module_access WHERE role_id = $1 AND module_id = $2",
    )
    .bind(role_id)
    .bind(module_id)
    .execute(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    if result.rows_affected() == 0 {
        return Err((StatusCode::NOT_FOUND, "Module access not found".to_string()));
    }

    Ok(StatusCode::NO_CONTENT)
}

#[derive(Serialize, sqlx::FromRow)]
struct RolePermissionRow {
    id: Uuid,
    module_id: Uuid,
    module_key: String,
    module_name: String,
    category: String,
    can_view: bool,
    can_list: bool,
    can_create: bool,
    can_update: bool,
    can_delete: bool,
}

async fn get_role_permissions(
    auth: AuthUser,
    State(state): State<AppState>,
    Path(role_id): Path<Uuid>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;

    let rows = sqlx::query_as::<_, RolePermissionRow>(
        r#"
        SELECT rmp.id, rmp.module_id, m.module_key, m.module_name, m.category,
               rmp.can_view, rmp.can_list, rmp.can_create, rmp.can_update, rmp.can_delete
        FROM role_module_permissions rmp
        JOIN modules m ON m.id = rmp.module_id
        WHERE rmp.role_id = $1
        ORDER BY m.category, m.module_name
        "#,
    )
    .bind(role_id)
    .fetch_all(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    Ok(Json(rows))
}

#[derive(Deserialize)]
struct UpdatePermissionPayload {
    module_key: String,
    permission: String,
    enabled: bool,
}

async fn update_role_permission(
    auth: AuthUser,
    State(state): State<AppState>,
    Path(role_id): Path<Uuid>,
    Json(payload): Json<UpdatePermissionPayload>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;

    let permission_col = match payload.permission.as_str() {
        "view" => "can_view",
        "list" => "can_list",
        "create" => "can_create",
        "update" => "can_update",
        "delete" => "can_delete",
        _ => return Err((StatusCode::BAD_REQUEST, "Invalid permission type".to_string())),
    };

    sqlx::query(&format!(
        r#"
        UPDATE role_module_permissions
        SET {} = $1
        WHERE role_id = $2 AND module_id = (SELECT id FROM modules WHERE module_key = $3)
        "#,
        permission_col
    ))
    .bind(payload.enabled)
    .bind(role_id)
    .bind(&payload.module_key)
    .execute(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    Ok(StatusCode::OK)
}