pub mod agent;
pub mod routes;

mod config;
pub(crate) mod db;
pub(crate) mod error;
mod resolve;
pub mod types;

// Re-exports for external use
pub use error::AppError;
pub use config::Config;
pub use agent::llm::ProviderRegistry;

// Internal re-exports for routes
pub(crate) use error::db_error_status;
pub(crate) use error::db_error_response;
pub(crate) use error::error_response;

// Re-exports from resolve module
pub(crate) use resolve::parse_uuid;
pub(crate) use resolve::resolve_project_id;
pub(crate) use resolve::resolve_edge_id;
pub(crate) use resolve::resolve_node_id;

use axum::{
    body::Body,
    extract::State,
    http::{Request, StatusCode},
    middleware,
    response::{Html, IntoResponse, Response},
    routing::get,
    Json, Router,
};
use tokio::sync::broadcast;
use tower_http::cors::{Any, CorsLayer};
use tower_http::services::ServeDir;
use tower_http::trace::{self, TraceLayer};
use tracing::Level;
use tracing_subscriber;

use crate::db::Database;
use crate::types::*;

#[derive(Clone)]
pub(crate) struct AppState {
    pub(crate) db: &'static Database,
    pub(crate) tx: broadcast::Sender<String>,
    pub(crate) config: Config,
    pub(crate) api_key: Option<String>,
    pub(crate) http_client: reqwest::Client,
    pub(crate) provider_registry: Option<ProviderRegistry>,
}

const API_KEY_HEADER: &str = "x-api-key";

fn unauthorized_response() -> Response {
    AppError::new(
        StatusCode::UNAUTHORIZED,
        "auth.invalid_api_key",
        "api_key_auth",
        "API key required or invalid.",
        "需要 API key 或 API key 无效。",
    )
    .with_hint(
        "Provide the x-api-key header, Authorization: Bearer token, or api_key query parameter.",
    )
    .into_response()
}

fn extract_api_key<B>(request: &Request<B>) -> Option<String> {
    if let Some(value) = request.headers().get(API_KEY_HEADER) {
        if let Ok(value) = value.to_str() {
            let trimmed = value.trim();
            if !trimmed.is_empty() {
                return Some(trimmed.to_string());
            }
        }
    }

    if let Some(value) = request.headers().get(axum::http::header::AUTHORIZATION) {
        if let Ok(value) = value.to_str() {
            if let Some(token) = value.strip_prefix("Bearer ") {
                let trimmed = token.trim();
                if !trimmed.is_empty() {
                    return Some(trimmed.to_string());
                }
            }
        }
    }

    request.uri().query().and_then(|query| {
        query.split('&').find_map(|pair| {
            let mut parts = pair.splitn(2, '=');
            let key = parts.next()?;
            let value = parts.next().unwrap_or_default();
            if key == "api_key" && !value.is_empty() {
                Some(value.to_string())
            } else {
                None
            }
        })
    })
}

async fn api_key_auth_middleware(
    State(state): State<AppState>,
    request: Request<Body>,
    next: middleware::Next,
) -> Response {
    let Some(expected_key) = state.api_key.as_deref() else {
        return next.run(request).await;
    };

    match extract_api_key(&request) {
        Some(provided_key) if provided_key == expected_key => next.run(request).await,
        _ => unauthorized_response(),
    }
}

async fn json_error_middleware(
    request: axum::extract::Request,
    next: middleware::Next,
) -> Response {
    let response = next.run(request).await;
    let status = response.status();
    if status.is_server_error() || status.is_client_error() {
        let (parts, body) = response.into_parts();
        let bytes = axum::body::to_bytes(body, 1024 * 1024)
            .await
            .unwrap_or_default();
        if bytes.is_empty() {
            let err = AppError::fallback_for_status(status);
            err.log();
            let json = Json(err.to_api_response::<()>());
            return (status, json).into_response();
        }
        return Response::from_parts(parts, Body::from(bytes));
    }
    response
}

/// Call DeepSeek chat API with retry for transient failures.
/// Returns response content or an HTTP error with a meaningful message.
pub(crate) async fn deepseek_chat(
    client: &reqwest::Client,
    api_key: &str,
    system_prompt: &str,
    user_content: &str,
    temperature: f64,
    max_tokens: u32,
    json_mode: bool,
) -> Result<String, StatusCode> {
    let mut body = serde_json::json!({
        "model": "deepseek-v4-flash",
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_content}
        ],
        "temperature": temperature,
        "max_tokens": max_tokens
    });
    if json_mode {
        body.as_object_mut().unwrap().insert(
            "response_format".to_string(),
            serde_json::json!({"type": "json_object"}),
        );
    }

    let max_retries = 3;
    let mut last_status = StatusCode::INTERNAL_SERVER_ERROR;

    for attempt in 0..max_retries {
        let res = client
            .post("https://api.deepseek.com/chat/completions")
            .header("Authorization", format!("Bearer {}", api_key))
            .json(&body)
            .send()
            .await
            .map_err(|e| {
                tracing::error!("DeepSeek API request failed: {}", e);
                StatusCode::INTERNAL_SERVER_ERROR
            })?;

        let status = res.status();
        if !status.is_success() {
            let body_text = res.text().await.unwrap_or_default();
            tracing::error!("DeepSeek API error {}: {}", status, body_text);

            // Retry on transient errors (429 rate limit, 502/503 gateway)
            if (status.as_u16() == 429 || status.as_u16() == 502 || status.as_u16() == 503)
                && attempt < max_retries - 1
            {
                let delay_ms = 1000 * 2u64.pow(attempt as u32);
                tracing::warn!(
                    "DeepSeek API {} — retrying in {}ms (attempt {}/{})",
                    status, delay_ms, attempt + 1, max_retries
                );
                tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)).await;
                last_status = status;
                continue;
            }

            return Err(match status.as_u16() {
                401 => StatusCode::INTERNAL_SERVER_ERROR,
                402 => StatusCode::INTERNAL_SERVER_ERROR,
                429 => StatusCode::INTERNAL_SERVER_ERROR,
                _ => StatusCode::INTERNAL_SERVER_ERROR,
            });
        }

        let body: serde_json::Value = res.json().await.map_err(|e| {
            tracing::error!("DeepSeek API response parse error: {}", e);
            StatusCode::INTERNAL_SERVER_ERROR
        })?;

        return body["choices"][0]["message"]["content"]
            .as_str()
            .map(String::from)
            .ok_or_else(|| {
                tracing::error!("DeepSeek API response missing content");
                StatusCode::INTERNAL_SERVER_ERROR
            });
    }

    tracing::error!("DeepSeek API failed after {} retries", max_retries);
    Err(last_status)
}

pub async fn serve(port: u16, deepseek_api_key: Option<String>, api_key: Option<String>) {
    tracing_subscriber::fmt()
        .with_env_filter("theseus_server=debug,tower_http=debug")
        .try_init()
        .ok();

    // Open (or create) SQLite database in project root
    let db_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../theseus.db");
    let db = Box::leak(Box::new(
        Database::open(db_path).expect("Failed to open database"),
    ));

    let (tx, _rx) = broadcast::channel(256);
    let config = crate::config::load_config(deepseek_api_key, api_key);
    
    // Initialize provider registry if providers are configured
    let provider_registry = if !config.providers.is_empty() {
        let registry = ProviderRegistry::new(&config);
        tracing::info!("AI features enabled with {} provider(s): {:?}",
            registry.providers.keys().collect::<Vec<_>>().len(),
            registry.providers.keys().collect::<Vec<_>>()
        );
        Some(registry)
    } else {
        tracing::info!("AI features disabled (no API key — use --api-key, THESEUS_DEEPSEEK_API_KEY, or theseus.toml)");
        None
    };

    if config.api_key.is_some() {
        tracing::info!("API key authentication enabled");
    } else {
        tracing::warn!("API key authentication disabled (server is open)");
    }
    let http_client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(120))
        .connect_timeout(std::time::Duration::from_secs(10))
        .build()
        .expect("Failed to build HTTP client");

    let state = AppState {
        db,
        tx: tx.clone(),
        api_key: config.api_key.clone(),
        config,
        http_client,
        provider_registry,
    };

    let api_router = Router::new()
        .merge(routes::ws::router())
        .merge(routes::projects::router())
        .merge(routes::nodes::router())
        .merge(routes::edges::router())
        .merge(routes::credentials::router())
        .merge(routes::timeline::router())
        .merge(routes::search::router())
        .merge(routes::evidence::router())
        .merge(routes::import::router())
        .merge(routes::analysis::router())
        .merge(routes::check::router())
        .merge(routes::report::router())
        .merge(routes::chat::router())
        .merge(routes::agent::router())
        .merge(routes::daily_report::router())
        .merge(routes::intents::router())
        .route_layer(axum::middleware::from_fn_with_state(
            state.clone(),
            api_key_auth_middleware,
        ));

    let cors = match &state.config.cors_origins {
        Some(origins) if !origins.is_empty() => {
            let origin_list: Vec<axum::http::HeaderValue> = origins
                .iter()
                .filter_map(|o| o.parse::<axum::http::HeaderValue>().ok())
                .collect();
            if origin_list.is_empty() {
                tracing::warn!("CORS: all configured origins invalid, falling back to localhost");
                CorsLayer::new()
                    .allow_origin([
                        "http://127.0.0.1:3080".parse::<axum::http::HeaderValue>().unwrap(),
                    ])
                    .allow_methods(Any)
                    .allow_headers(Any)
            } else {
                tracing::info!("CORS: allowing origins {:?}", origins);
                CorsLayer::new()
                    .allow_origin(origin_list)
                    .allow_methods(Any)
                    .allow_headers(Any)
            }
        }
        _ => {
            tracing::info!("CORS: no origins configured, defaulting to localhost:3080");
            CorsLayer::new()
                .allow_origin([
                    "http://127.0.0.1:3080".parse::<axum::http::HeaderValue>().unwrap(),
                ])
                .allow_methods(Any)
                .allow_headers(Any)
        }
    };

    let app = Router::new()
        .route("/", get(index))
        .route("/health", get(health))
        .merge(api_router)
        .nest_service(
            "/static",
            ServeDir::new(concat!(env!("CARGO_MANIFEST_DIR"), "/static")),
        )
        .layer(axum::middleware::from_fn(json_error_middleware))
        .layer(
            TraceLayer::new_for_http()
                .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
                .on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
        )
        .layer(cors)
        .with_state(state);

    async fn shutdown_signal() {
        let ctrl_c = async {
            tokio::signal::ctrl_c().await.expect("failed to install Ctrl+C handler");
        };

        #[cfg(unix)]
        let terminate = async {
            tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
                .expect("failed to install signal handler")
                .recv()
                .await;
        };

        #[cfg(not(unix))]
        let terminate = std::future::pending::<()>();

        tokio::select! {
            _ = ctrl_c => { tracing::info!("Received Ctrl+C, shutting down gracefully..."); },
            _ = terminate => { tracing::info!("Received SIGTERM, shutting down gracefully..."); },
        }
    }

    let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
        .await
        .unwrap();
    tracing::info!("Theseus server listening on http://0.0.0.0:{port}");
    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await
        .unwrap();
}

// ── index ──

async fn index() -> Html<&'static str> {
    Html(include_str!("../static/index.html"))
}

async fn health() -> Json<ApiResponse<serde_json::Value>> {
    Json(ApiResponse::ok(serde_json::json!({ "status": "ok" })))
}
