use crate::{
    db,
    dtos::{DailyMarketDataDto, TimeSeriesFinancialDto, RealtimeQuoteDto},
    AppState, ServerError,
};
use axum::{
    extract::{Path, Query, State},
    Json,
};
use chrono::NaiveDate;
use serde::Deserialize;
use service_kit::api;
use tracing::info;
use anyhow::Error as AnyhowError;

#[derive(Deserialize, utoipa::ToSchema)]
pub struct FinancialsQuery {
    metrics: Option<String>,
}

#[api(POST, "/api/v1/market-data/financials/batch")]
pub async fn batch_insert_financials(
    State(state): State<AppState>,
    Json(payload): Json<crate::dtos::TimeSeriesFinancialBatchDto>,
) -> Result<axum::http::StatusCode, ServerError> {
    info!(target: "api", count = payload.records.len(), "POST /market-data/financials/batch → batch_insert_financials called");
    db::batch_insert_financials(&state.pool, &payload.records).await.map_err(AnyhowError::from)?;
    info!(target: "api", count = payload.records.len(), "batch_insert_financials completed");
    Ok(axum::http::StatusCode::CREATED)
}

#[api(GET, "/api/v1/market-data/financials/{symbol}", output(list = "TimeSeriesFinancialDto"))]
pub async fn get_financials_by_symbol(
    State(state): State<AppState>,
    Path(symbol): Path<String>,
    Query(query): Query<FinancialsQuery>,
) -> Result<Json<Vec<TimeSeriesFinancialDto>>, ServerError> {
    info!(target: "api", symbol = %symbol, metrics = ?query.metrics, "GET /market-data/financials/{{symbol}} → get_financials_by_symbol called");
    let metrics = query.metrics.map(|s| s.split(',').map(String::from).collect());
    let financials = db::get_financials_by_symbol(&state.pool, &symbol, metrics).await.map_err(AnyhowError::from)?;

    // Convert Vec<Model> to Vec<Dto>
    let dtos: Vec<TimeSeriesFinancialDto> = financials
        .into_iter()
        .map(|f| TimeSeriesFinancialDto {
            symbol: f.symbol,
            metric_name: f.metric_name,
            period_date: f.period_date,
            value: f.value.try_into().unwrap_or(0.0), // Simplified conversion
            source: f.source,
        })
        .collect();

    info!(target: "api", symbol = %symbol, items = dtos.len(), "get_financials_by_symbol completed");
    Ok(Json(dtos))
}

// =================================================================================
// Realtime Quotes
// =================================================================================

#[derive(Deserialize, utoipa::ToSchema)]
pub struct RealtimeQuery {
    pub market: String,
    pub max_age_seconds: Option<i64>,
}

#[api(POST, "/api/v1/market-data/quotes")]
pub async fn upsert_realtime_quote(
    State(state): State<AppState>,
    Json(quote): Json<RealtimeQuoteDto>,
) -> Result<axum::http::StatusCode, ServerError> {
    info!(target: "api", symbol = %quote.symbol, market = %quote.market, "POST /market-data/quotes → upsert_realtime_quote called");
    db::insert_realtime_quote(&state.pool, &quote).await.map_err(AnyhowError::from)?;
    Ok(axum::http::StatusCode::CREATED)
}

#[api(GET, "/api/v1/market-data/quotes/{symbol}", output(detail = "RealtimeQuoteDto"))]
pub async fn get_latest_realtime_quote(
    State(state): State<AppState>,
    Path(symbol): Path<String>,
    Query(q): Query<RealtimeQuery>,
) -> Result<Json<RealtimeQuoteDto>, ServerError> {
    let market = q.market.clone();
    info!(target: "api", symbol = %symbol, market = %market, "GET /market-data/quotes/{{market}}/{{symbol}} → get_latest_realtime_quote called");
    if let Some(rec) = db::get_latest_realtime_quote(&state.pool, &market, &symbol).await.map_err(AnyhowError::from)? {
        if let Some(max_age) = q.max_age_seconds {
            let cutoff = chrono::Utc::now() - chrono::Duration::seconds(max_age);
            if rec.ts < cutoff {
                return Err(ServerError::NotFound("stale or missing quote".into()));
            }
        }
        let dto = RealtimeQuoteDto {
            symbol: rec.symbol,
            market: rec.market,
            ts: rec.ts,
            price: rec.price.try_into().unwrap_or(0.0),
            open_price: rec.open_price.and_then(|v| v.try_into().ok()),
            high_price: rec.high_price.and_then(|v| v.try_into().ok()),
            low_price: rec.low_price.and_then(|v| v.try_into().ok()),
            prev_close: rec.prev_close.and_then(|v| v.try_into().ok()),
            change: rec.change.and_then(|v| v.try_into().ok()),
            change_percent: rec.change_percent.and_then(|v| v.try_into().ok()),
            volume: rec.volume,
            source: rec.source,
        };
        Ok(Json(dto))
    } else {
        Err(ServerError::NotFound("quote not found".into()))
    }
}

#[derive(Deserialize, utoipa::ToSchema)]
pub struct DailyDataQuery {
    start_date: Option<NaiveDate>,
    end_date: Option<NaiveDate>,
}

#[api(POST, "/api/v1/market-data/daily/batch")]
pub async fn batch_insert_daily_data(
    State(state): State<AppState>,
    Json(payload): Json<crate::dtos::DailyMarketDataBatchDto>,
) -> Result<axum::http::StatusCode, ServerError> {
    info!(target: "api", count = payload.records.len(), "POST /market-data/daily/batch → batch_insert_daily_data called");
    db::batch_insert_daily_data(&state.pool, &payload.records).await.map_err(AnyhowError::from)?;
    info!(target: "api", count = payload.records.len(), "batch_insert_daily_data completed");
    Ok(axum::http::StatusCode::CREATED)
}

#[api(GET, "/api/v1/market-data/daily/{symbol}", output(list = "DailyMarketDataDto"))]
pub async fn get_daily_data_by_symbol(
    State(state): State<AppState>,
    Path(symbol): Path<String>,
    Query(query): Query<DailyDataQuery>,
) -> Result<Json<Vec<DailyMarketDataDto>>, ServerError> {
    info!(target: "api", symbol = %symbol, start = ?query.start_date, end = ?query.end_date, "GET /market-data/daily/{{symbol}} → get_daily_data_by_symbol called");
    let daily_data =
        db::get_daily_data_by_symbol(&state.pool, &symbol, query.start_date, query.end_date)
            .await
            .map_err(AnyhowError::from)?;

    // Convert Vec<Model> to Vec<Dto>
    let dtos: Vec<DailyMarketDataDto> = daily_data
        .into_iter()
        .map(|d| DailyMarketDataDto {
            symbol: d.symbol,
            trade_date: d.trade_date,
            open_price: d.open_price.and_then(|p| p.try_into().ok()),
            high_price: d.high_price.and_then(|p| p.try_into().ok()),
            low_price: d.low_price.and_then(|p| p.try_into().ok()),
            close_price: d.close_price.and_then(|p| p.try_into().ok()),
            volume: d.volume,
            pe: d.pe.and_then(|p| p.try_into().ok()),
            pb: d.pb.and_then(|p| p.try_into().ok()),
            total_mv: d.total_mv.and_then(|p| p.try_into().ok()),
        })
        .collect();

    info!(target: "api", symbol = %symbol, items = dtos.len(), "get_daily_data_by_symbol completed");
    Ok(Json(dtos))
}
