use axum::{
    Json,
    extract::{Path, Query, State},
    http::StatusCode,
};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::error::ApiError;
use crate::state::AppState;
use swym_dal::models::strategy::StrategyRow;
use swym_dal::models::strategy_config::{
    Condition, Expr, QuantitySpec, RuleBasedParams, SizingMethod, StrategyConfig,
};
use swym_dal::repo::strategy;
use swym_dal::strategy_hash::{compute_strategy_hash, normalize_strategy};

#[derive(Debug, Serialize)]
pub struct StrategyResponse {
    pub id: Uuid,
    pub hash: String,
    pub config: serde_json::Value,
    pub hash_input: serde_json::Value,
    pub created_at: DateTime<Utc>,
    pub name: Option<String>,
    pub version: i32,
    pub description: Option<String>,
}

impl From<StrategyRow> for StrategyResponse {
    fn from(row: StrategyRow) -> Self {
        Self {
            id: row.id,
            hash: row.hash,
            config: row.config,
            hash_input: row.hash_input,
            created_at: row.created_at,
            name: row.name,
            version: row.version,
            description: row.description,
        }
    }
}

#[derive(Debug, Deserialize)]
pub struct ListStrategiesQuery {
    #[serde(default = "default_limit")]
    pub limit: i64,
    #[serde(default)]
    pub offset: i64,
}

fn default_limit() -> i64 {
    50
}

#[derive(Debug, Deserialize)]
pub struct CreateStrategyRequest {
    pub config: serde_json::Value,
    /// Optional human-readable name for the strategy.
    pub name: Option<String>,
    /// Optional markdown description of the strategy's intent.
    pub description: Option<String>,
}

/// GET /api/v1/strategies
pub async fn list_strategies(
    State(state): State<AppState>,
    Query(query): Query<ListStrategiesQuery>,
) -> Result<Json<Vec<StrategyResponse>>, ApiError> {
    let rows = strategy::list(&state.pool, query.limit, query.offset).await?;
    let responses: Vec<StrategyResponse> = rows.into_iter().map(Into::into).collect();
    Ok(Json(responses))
}

/// GET /api/v1/strategies/{id}
pub async fn get_strategy(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> Result<Json<StrategyResponse>, ApiError> {
    let row = strategy::get_by_id(&state.pool, id)
        .await?
        .ok_or_else(|| ApiError::NotFound(format!("strategy {id} not found")))?;
    Ok(Json(row.into()))
}

/// POST /api/v1/strategies
///
/// Accepts a strategy config JSON (e.g. `{"type":"rule_based","candle_interval":"1h","rules":[...]}`),
/// validates it, computes the identity hash, and upserts into the strategies table.
/// Optional `name` and `description` fields are stored but not part of the content hash.
/// If `name` is provided the strategy is upserted by (name, version=1); otherwise by hash.
pub async fn create_strategy(
    State(state): State<AppState>,
    Json(req): Json<CreateStrategyRequest>,
) -> Result<(StatusCode, Json<StrategyResponse>), ApiError> {
    serde_json::from_value::<StrategyConfig>(req.config.clone())
        .map_err(|e| ApiError::BadRequest(format!("invalid strategy config: {e}")))?;

    let hash_input = normalize_strategy(&req.config);
    let hash = compute_strategy_hash(&hash_input);

    let row = match req.name {
        Some(ref name) => {
            strategy::upsert_named(
                &state.pool,
                name,
                1,
                req.description.as_deref().unwrap_or(""),
                &hash,
                &req.config,
                &hash_input,
            )
            .await?
        }
        None => strategy::upsert(&state.pool, &hash, &req.config, &hash_input).await?,
    };

    Ok((StatusCode::CREATED, Json(row.into())))
}

// ---------------------------------------------------------------------------
// POST /api/v1/strategies/validate
// ---------------------------------------------------------------------------

const VALID_INTERVALS: &[&str] = &["1m", "5m", "15m", "1h", "4h", "1d"];

/// A single validation error with an optional dotted JSON path.
#[derive(Debug, Serialize)]
pub struct ValidationError {
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub path: Option<String>,
}

impl ValidationError {
    fn at(path: impl Into<String>, message: impl Into<String>) -> Self {
        Self { message: message.into(), path: Some(path.into()) }
    }
    fn global(message: impl Into<String>) -> Self {
        Self { message: message.into(), path: None }
    }
}

#[derive(Debug, Serialize)]
pub struct ValidateStrategyResponse {
    pub valid: bool,
    pub errors: Vec<ValidationError>,
}

/// POST /api/v1/strategies/validate
///
/// Accepts the same strategy JSON that would go into a run config (`strategy` field),
/// runs the full deserialization pipeline, and returns every error as a structured list.
/// Always returns HTTP 200; `valid: false` means the strategy has errors.
///
/// Example request body:
/// ```json
/// { "type": "rule_based", "candle_interval": "5m", "rules": [...] }
/// ```
pub async fn validate_strategy(
    Json(body): Json<serde_json::Value>,
) -> Json<ValidateStrategyResponse> {
    // Stage 1: structural deserialization using serde_path_to_error for field paths.
    // Re-serialize to a string so we can hand a serde_json::Deserializer to
    // serde_path_to_error (serde_json::Value is not itself a Deserializer).
    let config: StrategyConfig = {
        let json_str = serde_json::to_string(&body)
            .expect("re-serializing a parsed Value should not fail");
        let mut json_de = serde_json::Deserializer::from_str(&json_str);
        match serde_path_to_error::deserialize(&mut json_de) {
            Ok(c) => c,
            Err(e) => {
                let path = e.path().to_string();
                let err = if path.is_empty() || path == "." {
                    ValidationError::global(e.inner().to_string())
                } else {
                    ValidationError::at(path, e.inner().to_string())
                };
                return Json(ValidateStrategyResponse { valid: false, errors: vec![err] });
            }
        }
    };

    // Stage 2: semantic validation — collects *all* errors.
    let mut errors: Vec<ValidationError> = Vec::new();

    match &config {
        StrategyConfig::Default => {
            // Default strategy has no further constraints.
        }
        StrategyConfig::RuleBased(params) => {
            validate_rule_based(params, &mut errors);
        }
    }

    // Stage 2b: unknown fields in sizing methods.
    // serde's deny_unknown_fields is not supported on internally-tagged enums
    // (#[serde(tag = "...")]) and silently does nothing, so we walk the raw JSON.
    check_sizing_method_unknown_fields(&body, "", &mut errors);

    let valid = errors.is_empty();
    Json(ValidateStrategyResponse { valid, errors })
}

fn validate_rule_based(params: &RuleBasedParams, errors: &mut Vec<ValidationError>) {
    // candle_interval must be a recognised value.
    if !VALID_INTERVALS.contains(&params.candle_interval.as_str()) {
        errors.push(ValidationError::at(
            "candle_interval",
            format!(
                "\"{}\" is not a valid interval; must be one of: {}",
                params.candle_interval,
                VALID_INTERVALS.join(", ")
            ),
        ));
    }

    if params.rules.is_empty() {
        errors.push(ValidationError::at("rules", "must contain at least one rule"));
    }

    for (i, rule) in params.rules.iter().enumerate() {
        let prefix = format!("rules[{i}]");
        validate_condition(&rule.when, &format!("{prefix}.when"), errors);
        validate_action_quantity(&rule.then.quantity, &format!("{prefix}.then.quantity"), errors);
    }
}

fn validate_condition(cond: &Condition, path: &str, errors: &mut Vec<ValidationError>) {
    match cond {
        Condition::EmaCrossover { fast_period, slow_period, timeframe, .. } => {
            if fast_period >= slow_period {
                errors.push(ValidationError::at(
                    path,
                    format!("fast_period ({fast_period}) must be less than slow_period ({slow_period})"),
                ));
            }
            validate_optional_timeframe(timeframe.as_deref(), path, errors);
        }
        Condition::EmaTrend { timeframe, .. }
        | Condition::Rsi { timeframe, .. }
        | Condition::Bollinger { timeframe, .. }
        | Condition::PriceLevel { timeframe, .. } => {
            validate_optional_timeframe(timeframe.as_deref(), path, errors);
        }
        Condition::AllOf { conditions } => {
            for (i, c) in conditions.iter().enumerate() {
                validate_condition(c, &format!("{path}.conditions[{i}]"), errors);
            }
        }
        Condition::AnyOf { conditions } => {
            for (i, c) in conditions.iter().enumerate() {
                validate_condition(c, &format!("{path}.conditions[{i}]"), errors);
            }
        }
        Condition::Not { condition } => {
            validate_condition(condition, &format!("{path}.condition"), errors);
        }
        Condition::EventCount { condition, .. } => {
            validate_condition(condition, &format!("{path}.condition"), errors);
        }
        Condition::Compare { left, right, .. } => {
            validate_expr(left, &format!("{path}.left"), errors);
            validate_expr(right, &format!("{path}.right"), errors);
        }
        Condition::CrossOver { left, right } | Condition::CrossUnder { left, right } => {
            validate_expr(left, &format!("{path}.left"), errors);
            validate_expr(right, &format!("{path}.right"), errors);
        }
        Condition::Position { .. } => {}
    }
}

fn validate_expr(expr: &Expr, path: &str, errors: &mut Vec<ValidationError>) {
    match expr {
        Expr::Func { name, .. } => {
            // apply_func does not support these; warn when used directly in Func
            // (Func itself is fine — just documenting no recursion needed here).
            let _ = name;
        }
        Expr::ApplyFunc { name, input, .. } => {
            use swym_dal::models::strategy_config::FuncName;
            if matches!(name, FuncName::Atr | FuncName::Adx | FuncName::Supertrend | FuncName::Rsi) {
                errors.push(ValidationError::at(
                    path,
                    format!("{name:?} cannot be used inside apply_func; it requires OHLC data"),
                ));
            }
            validate_expr(input, &format!("{path}.input"), errors);
        }
        Expr::BinOp { left, right, .. } => {
            validate_expr(left, &format!("{path}.left"), errors);
            validate_expr(right, &format!("{path}.right"), errors);
        }
        Expr::UnaryOp { operand, .. } => {
            validate_expr(operand, &format!("{path}.operand"), errors);
        }
        Expr::BarsSince { condition, .. } => {
            validate_condition(condition, &format!("{path}.condition"), errors);
        }
        // Leaf nodes (Literal, Field, Balance, EntryPrice, PositionQuantity,
        // UnrealisedPnl, BarsSinceEntry) need no semantic validation.
        _ => {}
    }
}

fn validate_action_quantity(qty: &QuantitySpec, path: &str, errors: &mut Vec<ValidationError>) {
    match qty {
        QuantitySpec::Fixed(d) => {
            if *d <= Decimal::ZERO {
                errors.push(ValidationError::at(path, "fixed quantity must be greater than zero"));
            }
        }
        QuantitySpec::Sizing(method) => match method.as_ref() {
            SizingMethod::FixedSum { amount } => {
                if *amount <= Decimal::ZERO {
                    errors.push(ValidationError::at(path, "fixed_sum amount must be greater than zero"));
                }
            }
            SizingMethod::PercentOfBalance { percent, .. } => {
                if *percent <= Decimal::ZERO {
                    errors.push(ValidationError::at(path, "percent_of_balance percent must be greater than zero"));
                } else if *percent > Decimal::ONE_HUNDRED {
                    errors.push(ValidationError::at(path, "percent_of_balance percent exceeds 100"));
                }
            }
            SizingMethod::FixedUnits { units } => {
                if *units <= Decimal::ZERO {
                    errors.push(ValidationError::at(path, "fixed_units must be greater than zero"));
                }
            }
        },
        QuantitySpec::Expr(e) => {
            validate_expr(e, path, errors);
        }
    }
}

fn check_sizing_method_unknown_fields(
    value: &serde_json::Value,
    path: &str,
    errors: &mut Vec<ValidationError>,
) {
    match value {
        serde_json::Value::Object(map) => {
            if map.contains_key("method") {
                let method = map.get("method").and_then(|v| v.as_str()).unwrap_or("");
                let known: &[&str] = match method {
                    "fixed_sum" => &["method", "amount"],
                    "percent_of_balance" => &["method", "percent", "asset"],
                    "fixed_units" => &["method", "units"],
                    _ => return, // unknown method is already caught by serde
                };
                for key in map.keys() {
                    if !known.contains(&key.as_str()) {
                        let field_path = if path.is_empty() {
                            key.clone()
                        } else {
                            format!("{path}.{key}")
                        };
                        errors.push(ValidationError::at(
                            field_path,
                            format!("unknown field `{key}` for sizing method `{method}`"),
                        ));
                    }
                }
            } else {
                for (key, val) in map {
                    let child = if path.is_empty() {
                        key.clone()
                    } else {
                        format!("{path}.{key}")
                    };
                    check_sizing_method_unknown_fields(val, &child, errors);
                }
            }
        }
        serde_json::Value::Array(arr) => {
            for (i, val) in arr.iter().enumerate() {
                let child = if path.is_empty() {
                    format!("[{i}]")
                } else {
                    format!("{path}[{i}]")
                };
                check_sizing_method_unknown_fields(val, &child, errors);
            }
        }
        _ => {}
    }
}

fn validate_optional_timeframe(tf: Option<&str>, path: &str, errors: &mut Vec<ValidationError>) {
    if let Some(t) = tf {
        if !VALID_INTERVALS.contains(&t) {
            errors.push(ValidationError::at(
                &format!("{path}.timeframe"),
                format!(
                    "\"{}\" is not a valid timeframe; must be one of: {}",
                    t,
                    VALID_INTERVALS.join(", ")
                ),
            ));
        }
    }
}
