//! REST HTTP API handlers for CreditService
//!
//! Implements REST endpoints as specified in T050.S7:
//! - GET /api/v1/wallets/{project_id} - Get wallet balance
//! - POST /api/v1/wallets - Create wallet
//! - POST /api/v1/wallets/{project_id}/topup - Top up credits
//! - GET /api/v1/wallets/{project_id}/transactions - Get transactions
//! - POST /api/v1/reservations - Reserve credits
//! - POST /api/v1/reservations/{id}/commit - Commit reservation
//! - POST /api/v1/reservations/{id}/release - Release reservation
//! - GET /health - Health check

use axum::{
    extract::{Path, State},
    http::{HeaderMap, StatusCode},
    routing::{get, post},
    Json, Router,
};
use creditservice_api::CreditServiceImpl;
use creditservice_proto::{
    credit_service_server::CreditService, CommitReservationRequest, CreateWalletRequest,
    GetTransactionsRequest, GetWalletRequest, ReleaseReservationRequest,
    Reservation as ProtoReservation, ReserveCreditsRequest, TopUpRequest,
    Transaction as ProtoTransaction, Wallet as ProtoWallet,
};
use photon_auth_client::{resolve_tenant_ids_from_context, AuthService, TenantContext};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tonic::{Code, Request};

/// REST API state
#[derive(Clone)]
pub struct RestApiState {
    pub credit_service: Arc<CreditServiceImpl>,
    pub auth_service: Arc<AuthService>,
}

/// 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(),
        }
    }
}

/// 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(),
        }
    }
}

/// Create wallet request
#[derive(Debug, Deserialize)]
pub struct CreateWalletRequestRest {
    pub project_id: String,
    pub org_id: String,
    pub initial_balance: Option<i64>,
}

/// Top up request
#[derive(Debug, Deserialize)]
pub struct TopUpRequestRest {
    pub amount: i64,
    pub description: Option<String>,
}

/// Reserve credits request
#[derive(Debug, Deserialize)]
pub struct ReserveCreditsRequestRest {
    pub project_id: String,
    pub amount: i64,
    pub description: Option<String>,
    pub resource_type: Option<String>,
    pub ttl_seconds: Option<i32>,
}

/// Commit reservation request
#[derive(Debug, Deserialize)]
pub struct CommitReservationRequestRest {
    pub actual_amount: Option<i64>,
    pub resource_id: Option<String>,
}

/// Release reservation request
#[derive(Debug, Deserialize)]
pub struct ReleaseReservationRequestRest {
    pub reason: Option<String>,
}

/// Wallet response
#[derive(Debug, Serialize)]
pub struct WalletResponse {
    pub project_id: String,
    pub org_id: String,
    pub balance: i64,
    pub reserved: i64,
    pub available: i64,
    pub total_deposited: i64,
    pub total_consumed: i64,
    pub status: String,
}

impl From<ProtoWallet> for WalletResponse {
    fn from(w: ProtoWallet) -> Self {
        let status = match w.status {
            1 => "active",
            2 => "suspended",
            3 => "closed",
            _ => "unknown",
        };
        Self {
            project_id: w.project_id,
            org_id: w.org_id,
            balance: w.balance,
            reserved: w.reserved,
            available: w.balance - w.reserved,
            total_deposited: w.total_deposited,
            total_consumed: w.total_consumed,
            status: status.to_string(),
        }
    }
}

/// Transaction response
#[derive(Debug, Serialize)]
pub struct TransactionResponse {
    pub id: String,
    pub project_id: String,
    pub transaction_type: String,
    pub amount: i64,
    pub balance_after: i64,
    pub description: String,
    pub resource_id: Option<String>,
}

impl From<ProtoTransaction> for TransactionResponse {
    fn from(t: ProtoTransaction) -> Self {
        let tx_type = match t.r#type {
            1 => "top_up",
            2 => "reservation",
            3 => "charge",
            4 => "release",
            5 => "refund",
            6 => "billing_charge",
            _ => "unknown",
        };
        Self {
            id: t.id,
            project_id: t.project_id,
            transaction_type: tx_type.to_string(),
            amount: t.amount,
            balance_after: t.balance_after,
            description: t.description,
            resource_id: if t.resource_id.is_empty() {
                None
            } else {
                Some(t.resource_id)
            },
        }
    }
}

/// Reservation response
#[derive(Debug, Serialize)]
pub struct ReservationResponse {
    pub id: String,
    pub project_id: String,
    pub amount: i64,
    pub status: String,
    pub description: String,
}

impl From<ProtoReservation> for ReservationResponse {
    fn from(r: ProtoReservation) -> Self {
        let status = match r.status {
            1 => "pending",
            2 => "committed",
            3 => "released",
            4 => "expired",
            _ => "unknown",
        };
        Self {
            id: r.id,
            project_id: r.project_id,
            amount: r.amount,
            status: status.to_string(),
            description: r.description,
        }
    }
}

/// Transactions list response
#[derive(Debug, Serialize)]
pub struct TransactionsResponse {
    pub transactions: Vec<TransactionResponse>,
    pub next_page_token: Option<String>,
}

/// Build the REST API router
pub fn build_router(state: RestApiState) -> Router {
    Router::new()
        .route("/api/v1/wallets", post(create_wallet))
        .route("/api/v1/wallets/{project_id}", get(get_wallet))
        .route("/api/v1/wallets/{project_id}/topup", post(topup))
        .route(
            "/api/v1/wallets/{project_id}/transactions",
            get(get_transactions),
        )
        .route("/api/v1/reservations", post(reserve_credits))
        .route("/api/v1/reservations/{id}/commit", post(commit_reservation))
        .route(
            "/api/v1/reservations/{id}/release",
            post(release_reservation),
        )
        .route("/health", get(health_check))
        .with_state(state)
}

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

/// GET /api/v1/wallets/{project_id} - Get wallet balance
async fn get_wallet(
    State(state): State<RestApiState>,
    Path(project_id): Path<String>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<WalletResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, Some(&project_id)).await?;
    let mut req = Request::new(GetWalletRequest { project_id });
    req.extensions_mut().insert(tenant);

    let response = state.credit_service.get_wallet(req).await.map_err(|e| {
        if e.code() == tonic::Code::NotFound {
            error_response(StatusCode::NOT_FOUND, "NOT_FOUND", "Wallet not found")
        } else {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "GET_FAILED",
                &e.message(),
            )
        }
    })?;

    let wallet = response
        .into_inner()
        .wallet
        .ok_or_else(|| error_response(StatusCode::NOT_FOUND, "NOT_FOUND", "Wallet not found"))?;

    Ok(Json(SuccessResponse::new(WalletResponse::from(wallet))))
}

/// POST /api/v1/wallets - Create wallet
async fn create_wallet(
    State(state): State<RestApiState>,
    headers: HeaderMap,
    Json(req): Json<CreateWalletRequestRest>,
) -> Result<(StatusCode, Json<SuccessResponse<WalletResponse>>), (StatusCode, Json<ErrorResponse>)>
{
    let tenant = resolve_rest_tenant(&state, &headers, Some(&req.project_id)).await?;
    let mut grpc_req = Request::new(CreateWalletRequest {
        project_id: req.project_id,
        org_id: req.org_id,
        initial_balance: req.initial_balance.unwrap_or(0),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .credit_service
        .create_wallet(grpc_req)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "CREATE_FAILED",
                &e.message(),
            )
        })?;

    let wallet = response.into_inner().wallet.ok_or_else(|| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "CREATE_FAILED",
            "No wallet returned",
        )
    })?;

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(WalletResponse::from(wallet))),
    ))
}

/// POST /api/v1/wallets/{project_id}/topup - Top up credits
async fn topup(
    State(state): State<RestApiState>,
    Path(project_id): Path<String>,
    headers: HeaderMap,
    Json(req): Json<TopUpRequestRest>,
) -> Result<Json<SuccessResponse<WalletResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, Some(&project_id)).await?;
    let mut grpc_req = Request::new(TopUpRequest {
        project_id,
        amount: req.amount,
        description: req.description.unwrap_or_default(),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state.credit_service.top_up(grpc_req).await.map_err(|e| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "TOPUP_FAILED",
            &e.message(),
        )
    })?;

    let wallet = response.into_inner().wallet.ok_or_else(|| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "TOPUP_FAILED",
            "No wallet returned",
        )
    })?;

    Ok(Json(SuccessResponse::new(WalletResponse::from(wallet))))
}

/// GET /api/v1/wallets/{project_id}/transactions - Get transactions
async fn get_transactions(
    State(state): State<RestApiState>,
    Path(project_id): Path<String>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<TransactionsResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, Some(&project_id)).await?;
    let mut req = Request::new(GetTransactionsRequest {
        project_id,
        page_size: 100,
        page_token: String::new(),
        type_filter: 0,
        start_time: None,
        end_time: None,
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .credit_service
        .get_transactions(req)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "LIST_FAILED",
                &e.message(),
            )
        })?;

    let inner = response.into_inner();
    let transactions: Vec<TransactionResponse> = inner
        .transactions
        .into_iter()
        .map(TransactionResponse::from)
        .collect();
    let next_page_token = if inner.next_page_token.is_empty() {
        None
    } else {
        Some(inner.next_page_token)
    };

    Ok(Json(SuccessResponse::new(TransactionsResponse {
        transactions,
        next_page_token,
    })))
}

/// POST /api/v1/reservations - Reserve credits
async fn reserve_credits(
    State(state): State<RestApiState>,
    headers: HeaderMap,
    Json(req): Json<ReserveCreditsRequestRest>,
) -> Result<
    (StatusCode, Json<SuccessResponse<ReservationResponse>>),
    (StatusCode, Json<ErrorResponse>),
> {
    let tenant = resolve_rest_tenant(&state, &headers, Some(&req.project_id)).await?;
    let mut grpc_req = Request::new(ReserveCreditsRequest {
        project_id: req.project_id,
        amount: req.amount,
        description: req.description.unwrap_or_default(),
        resource_type: req.resource_type.unwrap_or_default(),
        ttl_seconds: req.ttl_seconds.unwrap_or(300),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .credit_service
        .reserve_credits(grpc_req)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "RESERVE_FAILED",
                &e.message(),
            )
        })?;

    let reservation = response.into_inner().reservation.ok_or_else(|| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "RESERVE_FAILED",
            "No reservation returned",
        )
    })?;

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(ReservationResponse::from(reservation))),
    ))
}

/// POST /api/v1/reservations/{id}/commit - Commit reservation
async fn commit_reservation(
    State(state): State<RestApiState>,
    Path(reservation_id): Path<String>,
    headers: HeaderMap,
    Json(req): Json<CommitReservationRequestRest>,
) -> Result<Json<SuccessResponse<WalletResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, None).await?;
    let mut grpc_req = Request::new(CommitReservationRequest {
        reservation_id,
        actual_amount: req.actual_amount.unwrap_or(0),
        resource_id: req.resource_id.unwrap_or_default(),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .credit_service
        .commit_reservation(grpc_req)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "COMMIT_FAILED",
                &e.message(),
            )
        })?;

    let wallet = response.into_inner().wallet.ok_or_else(|| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "COMMIT_FAILED",
            "No wallet returned",
        )
    })?;

    Ok(Json(SuccessResponse::new(WalletResponse::from(wallet))))
}

/// POST /api/v1/reservations/{id}/release - Release reservation
async fn release_reservation(
    State(state): State<RestApiState>,
    Path(reservation_id): Path<String>,
    headers: HeaderMap,
    Json(req): Json<ReleaseReservationRequestRest>,
) -> Result<Json<SuccessResponse<serde_json::Value>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, None).await?;
    let mut grpc_req = Request::new(ReleaseReservationRequest {
        reservation_id: reservation_id.clone(),
        reason: req.reason.unwrap_or_default(),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .credit_service
        .release_reservation(grpc_req)
        .await
        .map_err(|e| {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                "RELEASE_FAILED",
                &e.message(),
            )
        })?;

    Ok(Json(SuccessResponse::new(serde_json::json!({
        "reservation_id": reservation_id,
        "released": response.into_inner().success
    }))))
}

/// Helper to create error response
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(),
        }),
    )
}

async fn resolve_rest_tenant(
    state: &RestApiState,
    headers: &HeaderMap,
    req_project_id: Option<&str>,
) -> Result<TenantContext, (StatusCode, Json<ErrorResponse>)> {
    let tenant = state
        .auth_service
        .authenticate_headers(headers)
        .await
        .map_err(map_auth_status)?;
    resolve_tenant_ids_from_context(&tenant, "", req_project_id.unwrap_or(""))
        .map_err(map_auth_status)?;
    Ok(tenant)
}

fn map_auth_status(status: tonic::Status) -> (StatusCode, Json<ErrorResponse>) {
    let status_code = match status.code() {
        Code::Unauthenticated => StatusCode::UNAUTHORIZED,
        Code::PermissionDenied => StatusCode::FORBIDDEN,
        Code::InvalidArgument => StatusCode::BAD_REQUEST,
        Code::NotFound => StatusCode::NOT_FOUND,
        _ => StatusCode::INTERNAL_SERVER_ERROR,
    };
    let code = match status.code() {
        Code::Unauthenticated => "UNAUTHENTICATED",
        Code::PermissionDenied => "FORBIDDEN",
        Code::InvalidArgument => "INVALID_ARGUMENT",
        Code::NotFound => "NOT_FOUND",
        _ => "INTERNAL",
    };

    error_response(status_code, code, status.message())
}
