//! REST HTTP API handlers for IAM.

use axum::{
    extract::{Path, Query, State},
    http::{HeaderMap, StatusCode},
    routing::{get, post},
    Json, Router,
};
use iam_client::client::{IamClient, IamClientConfig};
use iam_types::{Organization, Principal, PrincipalKind, PrincipalRef, Project, Scope};
use serde::{Deserialize, Serialize};

/// REST API state
#[derive(Clone)]
pub struct RestApiState {
    pub server_addr: String,
    pub tls_enabled: bool,
    pub admin_token: Option<String>,
}

/// Standard REST error response
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
    pub error: ErrorDetail,
    pub meta: ResponseMeta,
}

#[derive(Debug, Serialize)]
pub struct ErrorDetail {
    pub code: String,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<serde_json::Value>,
}

#[derive(Debug, Serialize)]
pub struct ResponseMeta {
    pub request_id: String,
    pub timestamp: String,
}

impl ResponseMeta {
    fn new() -> Self {
        Self {
            request_id: uuid::Uuid::new_v4().to_string(),
            timestamp: chrono::Utc::now().to_rfc3339(),
        }
    }
}

fn iam_client_config(state: &RestApiState) -> IamClientConfig {
    let mut config = IamClientConfig::new(&state.server_addr);
    if let Some(token) = state.admin_token.as_deref() {
        config = config.with_admin_token(token);
    }
    if !state.tls_enabled {
        config = config.without_tls();
    }
    config
}

/// Standard REST success response
#[derive(Debug, Serialize)]
pub struct SuccessResponse<T> {
    pub data: T,
    pub meta: ResponseMeta,
}

impl<T> SuccessResponse<T> {
    fn new(data: T) -> Self {
        Self {
            data,
            meta: ResponseMeta::new(),
        }
    }
}

#[derive(Debug, Deserialize)]
pub struct TokenRequest {
    pub username: String,
    pub password: String,
    #[serde(default = "default_ttl")]
    pub ttl_seconds: u64,
}

fn default_ttl() -> u64 {
    3600
}

#[derive(Debug, Serialize)]
pub struct TokenResponse {
    pub token: String,
    pub expires_at: String,
}

#[derive(Debug, Deserialize)]
pub struct VerifyRequest {
    pub token: String,
}

#[derive(Debug, Serialize)]
pub struct VerifyResponse {
    pub valid: bool,
    pub principal_id: Option<String>,
    pub principal_name: Option<String>,
    pub roles: Option<Vec<String>>,
}

#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
    pub id: String,
    pub name: String,
}

#[derive(Debug, Serialize)]
pub struct UserResponse {
    pub id: String,
    pub name: String,
    pub kind: String,
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub enabled: bool,
}

impl From<Principal> for UserResponse {
    fn from(p: Principal) -> Self {
        Self {
            id: p.id,
            name: p.name,
            kind: format!("{:?}", p.kind),
            org_id: p.org_id,
            project_id: p.project_id,
            enabled: p.enabled,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct UsersResponse {
    pub users: Vec<UserResponse>,
}

#[derive(Debug, Deserialize)]
pub struct CreateOrganizationRequest {
    pub id: String,
    pub name: String,
    #[serde(default)]
    pub description: String,
}

#[derive(Debug, Deserialize)]
pub struct UpdateOrganizationRequest {
    pub name: Option<String>,
    pub description: Option<String>,
    pub enabled: Option<bool>,
}

#[derive(Debug, Serialize)]
pub struct OrganizationResponse {
    pub id: String,
    pub name: String,
    pub description: String,
    pub enabled: bool,
}

impl From<Organization> for OrganizationResponse {
    fn from(org: Organization) -> Self {
        Self {
            id: org.id,
            name: org.name,
            description: org.description,
            enabled: org.enabled,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct OrganizationsResponse {
    pub organizations: Vec<OrganizationResponse>,
}

#[derive(Debug, Deserialize)]
pub struct CreateProjectRequest {
    pub id: String,
    pub org_id: String,
    pub name: String,
    #[serde(default)]
    pub description: String,
}

#[derive(Debug, Deserialize)]
pub struct UpdateProjectRequest {
    pub name: Option<String>,
    pub description: Option<String>,
    pub enabled: Option<bool>,
}

#[derive(Debug, Deserialize)]
pub struct ProjectsQuery {
    pub org_id: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct ProjectResponse {
    pub id: String,
    pub org_id: String,
    pub name: String,
    pub description: String,
    pub enabled: bool,
}

impl From<Project> for ProjectResponse {
    fn from(project: Project) -> Self {
        Self {
            id: project.id,
            org_id: project.org_id,
            name: project.name,
            description: project.description,
            enabled: project.enabled,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct ProjectsResponse {
    pub projects: Vec<ProjectResponse>,
}

pub fn build_router(state: RestApiState) -> Router {
    Router::new()
        .route("/api/v1/auth/token", post(issue_token))
        .route("/api/v1/auth/verify", post(verify_token))
        .route("/api/v1/users", get(list_users).post(create_user))
        .route("/api/v1/users/{id}", get(get_user))
        .route(
            "/api/v1/orgs",
            get(list_organizations).post(create_organization),
        )
        .route(
            "/api/v1/orgs/{org_id}",
            get(get_organization)
                .patch(update_organization)
                .delete(delete_organization),
        )
        .route("/api/v1/projects", get(list_projects).post(create_project))
        .route(
            "/api/v1/orgs/{org_id}/projects/{project_id}",
            get(get_project)
                .patch(update_project)
                .delete(delete_project),
        )
        .route("/health", get(health_check))
        .with_state(state)
}

async fn health_check() -> (StatusCode, Json<SuccessResponse<serde_json::Value>>) {
    (
        StatusCode::OK,
        Json(SuccessResponse::new(
            serde_json::json!({ "status": "healthy" }),
        )),
    )
}

fn require_admin(
    state: &RestApiState,
    headers: &HeaderMap,
) -> Result<(), (StatusCode, Json<ErrorResponse>)> {
    let Some(token) = state.admin_token.as_deref() else {
        return Ok(());
    };

    if let Some(value) = headers.get("x-iam-admin-token") {
        if let Ok(raw) = value.to_str() {
            if raw.trim() == token {
                return Ok(());
            }
        }
    }

    if let Some(value) = headers.get("authorization") {
        if let Ok(raw) = value.to_str() {
            let raw = raw.trim();
            if let Some(rest) = raw
                .strip_prefix("Bearer ")
                .or_else(|| raw.strip_prefix("bearer "))
            {
                if rest.trim() == token {
                    return Ok(());
                }
            }
        }
    }

    Err(error_response(
        StatusCode::UNAUTHORIZED,
        "ADMIN_TOKEN_REQUIRED",
        "missing or invalid IAM admin token",
    ))
}

async fn connect_client(
    state: &RestApiState,
) -> Result<IamClient, (StatusCode, Json<ErrorResponse>)> {
    IamClient::connect(iam_client_config(state))
        .await
        .map_err(|e| {
            error_response(
                StatusCode::SERVICE_UNAVAILABLE,
                "SERVICE_UNAVAILABLE",
                &format!("Failed to connect: {}", e),
            )
        })
}

async fn issue_token(
    headers: HeaderMap,
    State(state): State<RestApiState>,
    Json(req): Json<TokenRequest>,
) -> Result<Json<SuccessResponse<TokenResponse>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    if !allow_insecure_rest_token_issue() {
        return Err(error_response(
            StatusCode::FORBIDDEN,
            "TOKEN_ISSUE_DISABLED",
            "token issuance is disabled; enable IAM_REST_ALLOW_INSECURE_TOKEN=true for dev",
        ));
    }

    let TokenRequest {
        username,
        password: _password,
        ttl_seconds,
    } = req;

    let client = connect_client(&state).await?;
    let principal_ref = PrincipalRef::new(PrincipalKind::User, &username);
    let principal = match client.get_principal(&principal_ref).await.map_err(|e| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "PRINCIPAL_LOOKUP_FAILED",
            &e.to_string(),
        )
    })? {
        Some(principal) => principal,
        None => {
            return Err(error_response(
                StatusCode::NOT_FOUND,
                "PRINCIPAL_NOT_FOUND",
                "principal not found",
            ))
        }
    };

    if !principal.enabled {
        return Err(error_response(
            StatusCode::FORBIDDEN,
            "PRINCIPAL_DISABLED",
            "principal is disabled",
        ));
    }

    let token = client
        .issue_token(&principal, vec![], Scope::System, ttl_seconds)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "TOKEN_ISSUE_FAILED",
                &e.to_string(),
            )
        })?;

    let expires_at = chrono::Utc::now() + chrono::Duration::seconds(ttl_seconds as i64);

    Ok(Json(SuccessResponse::new(TokenResponse {
        token,
        expires_at: expires_at.to_rfc3339(),
    })))
}

fn allow_insecure_rest_token_issue() -> bool {
    std::env::var("IAM_REST_ALLOW_INSECURE_TOKEN")
        .or_else(|_| std::env::var("PHOTON_IAM_REST_ALLOW_INSECURE_TOKEN"))
        .ok()
        .map(|value| {
            matches!(
                value.trim().to_lowercase().as_str(),
                "1" | "true" | "yes" | "y" | "on"
            )
        })
        .unwrap_or(false)
}

async fn verify_token(
    State(state): State<RestApiState>,
    Json(req): Json<VerifyRequest>,
) -> Result<Json<SuccessResponse<VerifyResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let client = connect_client(&state).await?;
    let result = client.validate_token(&req.token).await;

    match result {
        Ok(claims) => Ok(Json(SuccessResponse::new(VerifyResponse {
            valid: true,
            principal_id: Some(claims.principal_id),
            principal_name: Some(claims.principal_name),
            roles: Some(claims.roles),
        }))),
        Err(_) => Ok(Json(SuccessResponse::new(VerifyResponse {
            valid: false,
            principal_id: None,
            principal_name: None,
            roles: None,
        }))),
    }
}

async fn create_user(
    headers: HeaderMap,
    State(state): State<RestApiState>,
    Json(req): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<SuccessResponse<UserResponse>>), (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let principal = client.create_user(&req.id, &req.name).await.map_err(|e| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "USER_CREATE_FAILED",
            &e.to_string(),
        )
    })?;

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(UserResponse::from(principal))),
    ))
}

async fn list_users(
    headers: HeaderMap,
    State(state): State<RestApiState>,
) -> Result<Json<SuccessResponse<UsersResponse>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let principals = client.list_users().await.map_err(|e| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "USER_LIST_FAILED",
            &e.to_string(),
        )
    })?;

    let users: Vec<UserResponse> = principals.into_iter().map(UserResponse::from).collect();
    Ok(Json(SuccessResponse::new(UsersResponse { users })))
}

async fn get_user(
    headers: HeaderMap,
    Path(id): Path<String>,
    State(state): State<RestApiState>,
) -> Result<Json<SuccessResponse<UserResponse>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let principal = client
        .get_principal(&PrincipalRef::user(id))
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "USER_LOOKUP_FAILED",
                &e.to_string(),
            )
        })?
        .ok_or_else(|| error_response(StatusCode::NOT_FOUND, "USER_NOT_FOUND", "user not found"))?;
    Ok(Json(SuccessResponse::new(UserResponse::from(principal))))
}

async fn create_organization(
    headers: HeaderMap,
    State(state): State<RestApiState>,
    Json(req): Json<CreateOrganizationRequest>,
) -> Result<
    (StatusCode, Json<SuccessResponse<OrganizationResponse>>),
    (StatusCode, Json<ErrorResponse>),
> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let org = client
        .create_organization(&req.id, &req.name, &req.description)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "ORG_CREATE_FAILED",
                &e.to_string(),
            )
        })?;
    Ok((StatusCode::CREATED, Json(SuccessResponse::new(org.into()))))
}

async fn list_organizations(
    headers: HeaderMap,
    State(state): State<RestApiState>,
) -> Result<Json<SuccessResponse<OrganizationsResponse>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let organizations = client.list_organizations().await.map_err(|e| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "ORG_LIST_FAILED",
            &e.to_string(),
        )
    })?;
    Ok(Json(SuccessResponse::new(OrganizationsResponse {
        organizations: organizations.into_iter().map(Into::into).collect(),
    })))
}

async fn get_organization(
    headers: HeaderMap,
    Path(org_id): Path<String>,
    State(state): State<RestApiState>,
) -> Result<Json<SuccessResponse<OrganizationResponse>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let organization = client
        .get_organization(&org_id)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "ORG_LOOKUP_FAILED",
                &e.to_string(),
            )
        })?
        .ok_or_else(|| {
            error_response(
                StatusCode::NOT_FOUND,
                "ORG_NOT_FOUND",
                "organization not found",
            )
        })?;
    Ok(Json(SuccessResponse::new(organization.into())))
}

async fn update_organization(
    headers: HeaderMap,
    Path(org_id): Path<String>,
    State(state): State<RestApiState>,
    Json(req): Json<UpdateOrganizationRequest>,
) -> Result<Json<SuccessResponse<OrganizationResponse>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let organization = client
        .update_organization(
            &org_id,
            req.name.as_deref(),
            req.description.as_deref(),
            req.enabled,
        )
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "ORG_UPDATE_FAILED",
                &e.to_string(),
            )
        })?;
    Ok(Json(SuccessResponse::new(organization.into())))
}

async fn delete_organization(
    headers: HeaderMap,
    Path(org_id): Path<String>,
    State(state): State<RestApiState>,
) -> Result<Json<SuccessResponse<serde_json::Value>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let deleted = client.delete_organization(&org_id).await.map_err(|e| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "ORG_DELETE_FAILED",
            &e.to_string(),
        )
    })?;
    Ok(Json(SuccessResponse::new(
        serde_json::json!({ "deleted": deleted }),
    )))
}

async fn create_project(
    headers: HeaderMap,
    State(state): State<RestApiState>,
    Json(req): Json<CreateProjectRequest>,
) -> Result<(StatusCode, Json<SuccessResponse<ProjectResponse>>), (StatusCode, Json<ErrorResponse>)>
{
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let project = client
        .create_project(&req.org_id, &req.id, &req.name, &req.description)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "PROJECT_CREATE_FAILED",
                &e.to_string(),
            )
        })?;
    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(project.into())),
    ))
}

async fn list_projects(
    headers: HeaderMap,
    Query(query): Query<ProjectsQuery>,
    State(state): State<RestApiState>,
) -> Result<Json<SuccessResponse<ProjectsResponse>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let projects = client
        .list_projects(query.org_id.as_deref())
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "PROJECT_LIST_FAILED",
                &e.to_string(),
            )
        })?;
    Ok(Json(SuccessResponse::new(ProjectsResponse {
        projects: projects.into_iter().map(Into::into).collect(),
    })))
}

async fn get_project(
    headers: HeaderMap,
    Path((org_id, project_id)): Path<(String, String)>,
    State(state): State<RestApiState>,
) -> Result<Json<SuccessResponse<ProjectResponse>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let project = client
        .get_project(&org_id, &project_id)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "PROJECT_LOOKUP_FAILED",
                &e.to_string(),
            )
        })?
        .ok_or_else(|| {
            error_response(
                StatusCode::NOT_FOUND,
                "PROJECT_NOT_FOUND",
                "project not found",
            )
        })?;
    Ok(Json(SuccessResponse::new(project.into())))
}

async fn update_project(
    headers: HeaderMap,
    Path((org_id, project_id)): Path<(String, String)>,
    State(state): State<RestApiState>,
    Json(req): Json<UpdateProjectRequest>,
) -> Result<Json<SuccessResponse<ProjectResponse>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let project = client
        .update_project(
            &org_id,
            &project_id,
            req.name.as_deref(),
            req.description.as_deref(),
            req.enabled,
        )
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "PROJECT_UPDATE_FAILED",
                &e.to_string(),
            )
        })?;
    Ok(Json(SuccessResponse::new(project.into())))
}

async fn delete_project(
    headers: HeaderMap,
    Path((org_id, project_id)): Path<(String, String)>,
    State(state): State<RestApiState>,
) -> Result<Json<SuccessResponse<serde_json::Value>>, (StatusCode, Json<ErrorResponse>)> {
    require_admin(&state, &headers)?;
    let client = connect_client(&state).await?;
    let deleted = client
        .delete_project(&org_id, &project_id)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "PROJECT_DELETE_FAILED",
                &e.to_string(),
            )
        })?;
    Ok(Json(SuccessResponse::new(
        serde_json::json!({ "deleted": deleted }),
    )))
}

fn error_response(
    status: StatusCode,
    code: &str,
    message: &str,
) -> (StatusCode, Json<ErrorResponse>) {
    (
        status,
        Json(ErrorResponse {
            error: ErrorDetail {
                code: code.to_string(),
                message: message.to_string(),
                details: None,
            },
            meta: ResponseMeta::new(),
        }),
    )
}
