use axum::http::StatusCode;
use db::models::ai::{
    AiCreditTransactionRepository, AiFeatureCost, AiFeatureCostRepository, AiPlan, AiPlanRepository,
    UserAiSubscription, UserAiSubscriptionRepository,
};
use sqlx::PgPool;
use uuid::Uuid;

#[derive(Debug, thiserror::Error)]
pub enum CreditError {
    #[error("Database error: {0}")]
    Db(#[from] sqlx::Error),
    #[error("Unknown feature '{feature}'")]
    UnknownFeature { feature: String },
    #[error("Insufficient AI credits")]
    InsufficientCredits,
    #[error("Daily AI action limit reached")]
    DailyActionLimitReached,
}

impl CreditError {
    pub fn status_code(&self) -> StatusCode {
        match self {
            CreditError::InsufficientCredits => StatusCode::PAYMENT_REQUIRED,
            CreditError::DailyActionLimitReached => StatusCode::TOO_MANY_REQUESTS,
            CreditError::UnknownFeature { .. } => StatusCode::BAD_REQUEST,
            CreditError::Db(_) => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }

    pub fn error_body(&self) -> serde_json::Value {
        serde_json::json!({
            "error": self.to_string(),
            "code": match self {
                CreditError::InsufficientCredits => "INSUFFICIENT_AI_CREDITS",
                CreditError::DailyActionLimitReached => "AI_DAILY_LIMIT_REACHED",
                CreditError::UnknownFeature { .. } => "UNKNOWN_AI_FEATURE",
                CreditError::Db(_) => "INTERNAL_ERROR",
            }
        })
    }
}

/// Remaining monthly credits (plan + purchased, minus used).
pub fn remaining_credits(sub: &UserAiSubscription) -> i32 {
    let total = sub.monthly_credits_total + sub.purchased_credits_total;
    let used = sub.monthly_credits_used + sub.purchased_credits_used;
    total.saturating_sub(used)
}

pub fn remaining_daily_actions(sub: &UserAiSubscription, plan: &AiPlan) -> i32 {
    plan.daily_action_limit.saturating_sub(sub.daily_actions_used)
}

pub async fn get_feature_cost(
    pool: &PgPool,
    feature_code: &str,
) -> Result<AiFeatureCost, CreditError> {
    AiFeatureCostRepository::get_by_code(pool, feature_code)
        .await?
        .ok_or_else(|| CreditError::UnknownFeature {
            feature: feature_code.to_string(),
        })
}

/// Charge credits for an AI feature. Increments daily_actions_used and
/// monthly_credits_used. Logs the transaction and usage. Returns the
/// resolved feature cost and model alias.
#[allow(clippy::too_many_arguments)]
pub async fn charge_feature(
    pool: &PgPool,
    user_id: Uuid,
    role_code: Option<&str>,
    feature_code: &str,
    requested_model: Option<&str>,
) -> Result<AiFeatureCost, CreditError> {
    let cost = get_feature_cost(pool, feature_code).await?;
    let model_alias = requested_model.unwrap_or(&cost.default_model);

    let sub = UserAiSubscriptionRepository::get_by_user_id(pool, user_id)
        .await?
        .ok_or(CreditError::InsufficientCredits)?;

    let plan = AiPlanRepository::get_by_id(pool, sub.plan_id)
        .await?
        .ok_or(CreditError::InsufficientCredits)?;

    // Single atomic conditional UPDATE — enforces the credit balance and
    // daily action limit and deducts in the same statement, so concurrent
    // requests from the same user can't all pass a separate check before
    // any of them commits a deduction (which would let a user run up
    // unbounded LLM spend by firing requests in parallel).
    let charged = UserAiSubscriptionRepository::try_charge(
        pool,
        user_id,
        cost.credit_cost,
        plan.daily_action_limit,
    )
    .await?;

    let sub = match charged {
        Some(updated) => updated,
        None => {
            if sub.daily_actions_used >= plan.daily_action_limit {
                return Err(CreditError::DailyActionLimitReached);
            }
            return Err(CreditError::InsufficientCredits);
        }
    };

    // Record transaction.
    let balance_after = remaining_credits(&sub);
    AiCreditTransactionRepository::create(
        pool,
        user_id,
        "debit",
        "usage",
        -cost.credit_cost,
        balance_after,
        None,
        Some(&format!("feature={}", feature_code)),
    )
    .await?;

    let _ = super::usage::log_usage(
        pool,
        user_id,
        role_code,
        feature_code,
        model_alias,
        cost.credit_cost,
        None,
        None,
        None,
        "success",
        None,
        None,
    )
    .await;

    Ok(AiFeatureCost {
        id: cost.id,
        feature_code: cost.feature_code,
        display_name: cost.display_name,
        default_model: model_alias.to_string(),
        credit_cost: cost.credit_cost,
        max_input_tokens: cost.max_input_tokens,
        max_output_tokens: cost.max_output_tokens,
        is_active: cost.is_active,
        created_at: cost.created_at,
        updated_at: cost.updated_at,
    })
}

/// Validate that a user could afford a feature without charging. Useful for
/// pre-flight checks in streaming endpoints.
#[allow(dead_code)]
pub async fn can_afford(pool: &PgPool, user_id: Uuid, feature_code: &str) -> Result<bool, CreditError> {
    let cost = get_feature_cost(pool, feature_code).await?;
    let sub = UserAiSubscriptionRepository::get_by_user_id(pool, user_id)
        .await?
        .ok_or(CreditError::InsufficientCredits)?;
    Ok(remaining_credits(&sub) >= cost.credit_cost)
}
