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

pub fn router() -> Router<AppState> {
    Router::new()
        .route("/", get(list_roles).post(create_role))
        .route("/{id}", get(get_role).patch(update_role).delete(delete_role))
}

#[derive(Deserialize)]
struct ListQuery {
    q: Option<String>,
    page: Option<i64>,
    per_page: Option<i64>,
}

#[derive(Serialize)]
struct RoleRow {
    id: Uuid,
    key: String,
    name: String,
    audience: String,
    description: Option<String>,
    department_id: Option<Uuid>,
    department_name: Option<String>,
    is_active: bool,
    can_approve_requests: bool,
    can_manage_system_settings: bool,
    users_assigned: i64,
    permissions_count: i64,
    created_at: chrono::DateTime<chrono::Utc>,
}

#[derive(Serialize)]
struct ListResponse {
    roles: Vec<RoleRow>,
    total: i64,
    page: i64,
    per_page: i64,
}

#[derive(Serialize)]
struct RoleDetail {
    id: Uuid,
    key: String,
    name: String,
    audience: String,
    description: Option<String>,
    department_id: Option<Uuid>,
    department_name: Option<String>,
    is_active: bool,
    can_approve_requests: bool,
    can_manage_system_settings: bool,
    permission_keys: Vec<String>,
    created_at: chrono::DateTime<chrono::Utc>,
}

#[derive(Deserialize)]
struct CreateRolePayload {
    key: String,
    name: String,
    description: Option<String>,
    department_id: Option<Uuid>,
    is_active: Option<bool>,
    can_approve_requests: Option<bool>,
    can_manage_system_settings: Option<bool>,
    permission_keys: Option<Vec<String>>,
}

#[derive(Deserialize)]
struct UpdateRolePayload {
    name: Option<String>,
    description: Option<String>,
    department_id: Option<Uuid>,
    is_active: Option<bool>,
    can_approve_requests: Option<bool>,
    can_manage_system_settings: Option<bool>,
    permission_keys: Option<Vec<String>>,
}

#[derive(sqlx::FromRow)]
struct RoleListRow {
    id: Uuid,
    key: String,
    name: String,
    audience: String,
    description: Option<String>,
    department_id: Option<Uuid>,
    department_name: Option<String>,
    is_active: bool,
    can_approve_requests: bool,
    can_manage_system_settings: bool,
    created_at: chrono::DateTime<chrono::Utc>,
    users_assigned: Option<i64>,
    permissions_count: Option<i64>,
}

#[derive(sqlx::FromRow)]
struct RoleDetailRow {
    id: Uuid,
    key: String,
    name: String,
    audience: String,
    description: Option<String>,
    department_id: Option<Uuid>,
    department_name: Option<String>,
    is_active: bool,
    can_approve_requests: bool,
    can_manage_system_settings: bool,
    created_at: chrono::DateTime<chrono::Utc>,
}

#[derive(sqlx::FromRow)]
struct InsertedRoleRow {
    id: Uuid,
    key: String,
    name: String,
    audience: String,
    is_active: bool,
    created_at: chrono::DateTime<chrono::Utc>,
}

#[derive(sqlx::FromRow)]
struct CurrentRoleRow {
    name: String,
    description: Option<String>,
    department_id: Option<Uuid>,
    is_active: bool,
    can_approve_requests: bool,
    can_manage_system_settings: bool,
}

async fn list_roles(
    auth: AuthUser,
    State(state): State<AppState>,
    Query(params): Query<ListQuery>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;
    let page = params.page.unwrap_or(1).max(1);
    let per_page = params.per_page.unwrap_or(20).min(100);
    let offset = (page - 1) * per_page;
    let search = params.q.as_deref().unwrap_or("").to_lowercase();

    let rows = sqlx::query_as::<_, RoleListRow>(
        r#"
        SELECT
            r.id,
            r.key,
            r.name,
            r.audience,
            ir.description,
            ir.department_id,
            d.name AS department_name,
            r.is_active,
            COALESCE(ir.can_approve_requests, false) AS can_approve_requests,
            COALESCE(ir.can_manage_system_settings, false) AS can_manage_system_settings,
            r.created_at,
            COUNT(DISTINCT e.id) AS users_assigned,
            COUNT(DISTINCT rp.id) AS permissions_count
        FROM roles r
        JOIN internal_role_details ir ON ir.role_id = r.id
        LEFT JOIN departments d ON d.id = ir.department_id
        LEFT JOIN employees e ON e.role_code = r.key
        LEFT JOIN role_admin_permissions rp ON rp.role_id = r.id
        WHERE r.audience = 'INTERNAL'
          AND ($1 = '' OR LOWER(r.name) LIKE '%' || $1 || '%' OR LOWER(r.key) LIKE '%' || $1 || '%')
        GROUP BY r.id, ir.description, ir.department_id, ir.can_approve_requests, ir.can_manage_system_settings, d.name
        ORDER BY r.created_at DESC
        LIMIT $2 OFFSET $3
        "#,
    )
    .bind(&search)
    .bind(per_page)
    .bind(offset)
    .fetch_all(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    let total: i64 = sqlx::query_scalar::<_, i64>(
        r#"
        SELECT COUNT(*) FROM roles r
        JOIN internal_role_details ir ON ir.role_id = r.id
        WHERE r.audience = 'INTERNAL'
          AND ($1 = '' OR LOWER(r.name) LIKE '%' || $1 || '%' OR LOWER(r.key) LIKE '%' || $1 || '%')
        "#,
    )
    .bind(&search)
    .fetch_one(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    let roles = rows
        .into_iter()
        .map(|r| RoleRow {
            id: r.id,
            key: r.key,
            name: r.name,
            audience: r.audience,
            description: r.description,
            department_id: r.department_id,
            department_name: r.department_name,
            is_active: r.is_active,
            can_approve_requests: r.can_approve_requests,
            can_manage_system_settings: r.can_manage_system_settings,
            users_assigned: r.users_assigned.unwrap_or(0),
            permissions_count: r.permissions_count.unwrap_or(0),
            created_at: r.created_at,
        })
        .collect();

    Ok(Json(ListResponse { roles, total, page, per_page }))
}

async fn get_role(
    auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;
    let row = sqlx::query_as::<_, RoleDetailRow>(
        r#"
        SELECT
            r.id, r.key, r.name, r.audience,
            ir.description,
            ir.department_id, d.name AS department_name,
            r.is_active,
            COALESCE(ir.can_approve_requests, false) AS can_approve_requests,
            COALESCE(ir.can_manage_system_settings, false) AS can_manage_system_settings,
            r.created_at
        FROM roles r
        JOIN internal_role_details ir ON ir.role_id = r.id
        LEFT JOIN departments d ON d.id = ir.department_id
        WHERE r.id = $1 AND r.audience = 'INTERNAL'
        "#,
    )
    .bind(id)
    .fetch_optional(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?
    .ok_or((StatusCode::NOT_FOUND, "Role not found".to_string()))?;

    let permission_keys: Vec<String> = sqlx::query_scalar::<_, String>(
        "SELECT permission_key FROM role_admin_permissions WHERE role_id = $1 ORDER BY permission_key",
    )
    .bind(id)
    .fetch_all(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    Ok(Json(RoleDetail {
        id: row.id,
        key: row.key,
        name: row.name,
        audience: row.audience,
        description: row.description,
        department_id: row.department_id,
        department_name: row.department_name,
        is_active: row.is_active,
        can_approve_requests: row.can_approve_requests,
        can_manage_system_settings: row.can_manage_system_settings,
        permission_keys,
        created_at: row.created_at,
    }))
}

async fn create_role(
    auth: AuthUser,
    State(state): State<AppState>,
    Json(payload): Json<CreateRolePayload>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;
    let is_active = payload.is_active.unwrap_or(true);
    let can_approve = payload.can_approve_requests.unwrap_or(false);
    let can_manage = payload.can_manage_system_settings.unwrap_or(false);

    let role = sqlx::query_as::<_, InsertedRoleRow>(
        r#"
        INSERT INTO roles (key, name, audience, is_active)
        VALUES ($1, $2, 'INTERNAL', $3)
        RETURNING id, key, name, audience, is_active, created_at
        "#,
    )
    .bind(&payload.key)
    .bind(&payload.name)
    .bind(is_active)
    .fetch_one(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    sqlx::query(
        r#"
        INSERT INTO internal_role_details (role_id, description, department_id, can_approve_requests, can_manage_system_settings)
        VALUES ($1, $2, $3, $4, $5)
        "#,
    )
    .bind(role.id)
    .bind(&payload.description)
    .bind(payload.department_id)
    .bind(can_approve)
    .bind(can_manage)
    .execute(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    if let Some(keys) = &payload.permission_keys {
        for key in keys {
            sqlx::query(
                "INSERT INTO role_admin_permissions (role_id, permission_key) VALUES ($1, $2) ON CONFLICT DO NOTHING",
            )
            .bind(role.id)
            .bind(key)
            .execute(&state.pool)
            .await
            .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
        }
    }

    let permission_keys: Vec<String> = sqlx::query_scalar::<_, String>(
        "SELECT permission_key FROM role_admin_permissions WHERE role_id = $1 ORDER BY permission_key",
    )
    .bind(role.id)
    .fetch_all(&state.pool)
    .await
    .unwrap_or_default();

    Ok((
        StatusCode::CREATED,
        Json(RoleDetail {
            id: role.id,
            key: role.key,
            name: role.name,
            audience: role.audience,
            description: payload.description,
            department_id: payload.department_id,
            department_name: None,
            is_active: role.is_active,
            can_approve_requests: can_approve,
            can_manage_system_settings: can_manage,
            permission_keys,
            created_at: role.created_at,
        }),
    ))
}

async fn update_role(
    auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
    Json(payload): Json<UpdateRolePayload>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;
    let current = sqlx::query_as::<_, CurrentRoleRow>(
        r#"
        SELECT r.name, ir.description, ir.department_id, r.is_active,
               COALESCE(ir.can_approve_requests, false) AS can_approve_requests,
               COALESCE(ir.can_manage_system_settings, false) AS can_manage_system_settings
        FROM roles r
        JOIN internal_role_details ir ON ir.role_id = r.id
        WHERE r.id = $1 AND r.audience = 'INTERNAL'
        "#,
    )
    .bind(id)
    .fetch_optional(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?
    .ok_or((StatusCode::NOT_FOUND, "Role not found".to_string()))?;

    let name = payload.name.unwrap_or(current.name);
    let is_active = payload.is_active.unwrap_or(current.is_active);

    sqlx::query(
        "UPDATE roles SET name = $1, is_active = $2 WHERE id = $3",
    )
    .bind(&name)
    .bind(is_active)
    .bind(id)
    .execute(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    let description = payload.description.or(current.description);
    let department_id = payload.department_id.or(current.department_id);
    let can_approve = payload.can_approve_requests.unwrap_or(current.can_approve_requests);
    let can_manage = payload.can_manage_system_settings.unwrap_or(current.can_manage_system_settings);

    sqlx::query(
        r#"
        UPDATE internal_role_details SET
            description = $1,
            department_id = $2,
            can_approve_requests = $3,
            can_manage_system_settings = $4
        WHERE role_id = $5
        "#,
    )
    .bind(&description)
    .bind(department_id)
    .bind(can_approve)
    .bind(can_manage)
    .bind(id)
    .execute(&state.pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

    if let Some(keys) = &payload.permission_keys {
        sqlx::query("DELETE FROM role_admin_permissions WHERE role_id = $1")
            .bind(id)
            .execute(&state.pool)
            .await
            .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;

        for key in keys {
            sqlx::query(
                "INSERT INTO role_admin_permissions (role_id, permission_key) VALUES ($1, $2) ON CONFLICT DO NOTHING",
            )
            .bind(id)
            .bind(key)
            .execute(&state.pool)
            .await
            .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
        }
    }

    get_role(auth, State(state), Path(id)).await
}

async fn delete_role(
    auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;
    let result = sqlx::query("DELETE FROM roles WHERE id = $1 AND audience = 'INTERNAL'")
        .bind(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, "Role not found".to_string()));
    }

    Ok(StatusCode::NO_CONTENT)
}
