//! KRX connector implementation

use reqwest::Client;
use std::collections::HashMap;

use crate::core::types::*;
use crate::core::traits::*;

use super::endpoints::*;
use super::auth::*;
use super::parser::*;


/// KRX (Korea Exchange) connector
pub struct KrxConnector {
    client: Client,
    auth: KrxAuth,
    endpoints: KrxEndpoints,
}

impl KrxConnector {
    /// Create new KRX connector with authentication
    pub fn new(auth: KrxAuth) -> Self {
        Self {
            client: Client::new(),
            auth,
            endpoints: KrxEndpoints::default(),
        }
    }

    /// Create public connector without API keys
    ///
    /// WARNING: The new KRX Open API requires authentication.
    /// This constructor is kept for backward compatibility but most methods
    /// will fail with Auth error unless PUBLIC_DATA_PORTAL_KEY is set.
    ///
    /// The old web-scraping pattern (data.krx.co.kr) is DEPRECATED and returns "LOGOUT".
    /// Users must register at https://openapi.krx.co.kr/ to obtain AUTH_KEY.
    #[deprecated(
        since = "0.1.0",
        note = "KRX now requires authentication. Use new() with KrxAuth::from_env() or obtain keys from openapi.krx.co.kr"
    )]
    pub fn new_public() -> Self {
        Self {
            client: Client::new(),
            auth: KrxAuth {
                auth_key: None,
                public_data_portal_key: None,
            },
            endpoints: KrxEndpoints::default(),
        }
    }

    /// Create connector from environment variables
    pub fn from_env() -> Self {
        Self::new(KrxAuth::from_env())
    }

    /// Make POST request to Open API
    ///
    /// All Open API requests are JSON POST with {"basDd": "YYYYMMDD"} body format
    async fn post_openapi(
        &self,
        endpoint: KrxEndpoint,
        body: serde_json::Value,
    ) -> ExchangeResult<serde_json::Value> {
        // Check for authentication
        if !self.auth.has_openapi_auth() {
            return Err(ExchangeError::Auth(
                "KRX Open API requires AUTH_KEY. Register at https://openapi.krx.co.kr/ and set KRX_AUTH_KEY environment variable".to_string(),
            ));
        }

        let url = format!("{}{}", self.endpoints.openapi_base, endpoint.path());

        // Prepare headers with auth
        let mut headers = HashMap::new();
        self.auth.sign_openapi_headers(&mut headers);

        // Build request
        let mut request = self.client.post(&url);

        // Add headers
        for (key, value) in headers {
            request = request.header(key, value);
        }

        // Add JSON body
        request = request.json(&body);

        // Send request
        let response = request
            .send()
            .await
            .map_err(|e| ExchangeError::Network(format!("Request failed: {}", e)))?;

        // Check HTTP status
        let status = response.status();

        // Get response text for parsing
        let response_text = response
            .text()
            .await
            .map_err(|e| ExchangeError::Network(format!("Failed to read response: {}", e)))?;

        if !status.is_success() {
            return Err(match status.as_u16() {
                401 => ExchangeError::Auth(format!("API key not authorized: {}", response_text)),
                403 => ExchangeError::PermissionDenied(format!("Access forbidden - check API permissions: {}", response_text)),
                429 => ExchangeError::RateLimit,
                _ => ExchangeError::Http(format!("HTTP {}: {}", status, response_text)),
            });
        }

        // Parse JSON response
        let json: serde_json::Value = serde_json::from_str(&response_text)
            .map_err(|e| ExchangeError::Parse(format!("JSON parse error: {}. Response: {}", e, response_text)))?;

        // Check for API errors in new format
        KrxParser::check_api_error(&json)?;

        Ok(json)
    }

    /// POST form-encoded request to KRX Data Marketplace (no auth required)
    ///
    /// Endpoint: `http://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd`
    /// Requires a browser-like User-Agent header or the server returns a block page.
    async fn post_data_marketplace(
        &self,
        params: &[(&str, &str)],
    ) -> ExchangeResult<serde_json::Value> {
        let url = self.endpoints.data_marketplace;

        let response = self
            .client
            .post(url)
            .header(
                "User-Agent",
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
            )
            .header("Referer", "http://data.krx.co.kr/")
            .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
            .form(params)
            .send()
            .await
            .map_err(|e| ExchangeError::Network(format!("KRX marketplace request failed: {}", e)))?;

        if !response.status().is_success() {
            let status = response.status();
            let body = response.text().await.unwrap_or_default();
            return Err(ExchangeError::Http(format!("HTTP {}: {}", status, body)));
        }

        let json: serde_json::Value = response
            .json()
            .await
            .map_err(|e| ExchangeError::Parse(format!("JSON parse error: {}", e)))?;

        Ok(json)
    }

    /// Make GET request to Public Data Portal API
    async fn get_portal(&self, mut params: HashMap<String, String>) -> ExchangeResult<serde_json::Value> {
        let url = self.endpoints.public_data_portal;

        // Add authentication
        self.auth.sign_portal_query(&mut params);

        // Add default params
        params.entry("resultType".to_string()).or_insert("json".to_string());
        params.entry("numOfRows".to_string()).or_insert("100".to_string());
        params.entry("pageNo".to_string()).or_insert("1".to_string());

        // Send request
        let response = self
            .client
            .get(url)
            .query(&params)
            .send()
            .await
            .map_err(|e| ExchangeError::Network(format!("Request failed: {}", e)))?;

        // Check HTTP status
        if !response.status().is_success() {
            return Err(ExchangeError::Http(format!("HTTP {}", response.status())));
        }

        // Parse JSON response
        let json = response
            .json()
            .await
            .map_err(|e| ExchangeError::Parse(format!("JSON parse error: {}", e)))?;

        // Check for API errors
        KrxParser::check_api_error(&json)?;

        Ok(json)
    }

}

// ═══════════════════════════════════════════════════════════════════════════
// TRAIT: ExchangeIdentity
// ═══════════════════════════════════════════════════════════════════════════

impl ExchangeIdentity for KrxConnector {
    fn exchange_name(&self) -> &'static str {
        "krx"
    }

    fn exchange_id(&self) -> ExchangeId {
        ExchangeId::Krx
    }

    fn is_testnet(&self) -> bool {
        false
    }

    fn supported_account_types(&self) -> Vec<AccountType> {
        // KRX is data provider only - use Spot as equivalent
        vec![AccountType::Spot]
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// TRAIT: MarketData
// ═══════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl MarketData for KrxConnector {
    /// Get current price via Data Marketplace (last daily close)
    async fn get_price(&self, symbol: SymbolInput<'_>, account_type: AccountType) -> ExchangeResult<Price> {
        let klines = self.get_klines(symbol, "1d", Some(1), account_type, None).await?;
        klines
            .first()
            .map(|k| k.close)
            .ok_or_else(|| ExchangeError::NotFound("No data returned for symbol".to_string()))
    }

    /// Get ticker (24h stats) via Data Marketplace (last daily bar)
    async fn get_ticker(&self, symbol: SymbolInput<'_>, account_type: AccountType) -> ExchangeResult<Ticker> {
        let sym_str: String = match symbol {
            SymbolInput::Raw(s) => s.to_string(),
            SymbolInput::Canonical(c) => c.to_concat(),
        };
        let klines = self
            .get_klines(SymbolInput::Raw(&sym_str), "1d", Some(1), account_type, None)
            .await?;
        let k = klines
            .first()
            .ok_or_else(|| ExchangeError::NotFound("No data returned for symbol".to_string()))?;
        Ok(Ticker {
            last_price: k.close,
            bid_price: None,
            ask_price: None,
            high_24h: Some(k.high),
            low_24h: Some(k.low),
            volume_24h: Some(k.volume),
            quote_volume_24h: k.quote_volume,
            price_change_24h: None,
            price_change_percent_24h: None,
            timestamp: k.open_time, ..Default::default() 
        })
    }

    /// Get orderbook
    ///
    /// KRX does not provide orderbook data through public API
    async fn get_orderbook(
        &self,
        _symbol: SymbolInput<'_>,
        _depth: Option<u16>,
        _account_type: AccountType,
    ) -> ExchangeResult<OrderBook> {
        Err(ExchangeError::NotImplemented(
            "KRX does not provide orderbook data - data feed only".to_string(),
        ))
    }

    /// Get klines/candles via KRX Data Marketplace (public, no auth required)
    ///
    /// KRX only provides daily (`1d`) candles.
    /// `limit` controls how many calendar days back to start (default 30).
    async fn get_klines(
        &self,
        symbol: SymbolInput<'_>,
        interval: &str,
        limit: Option<u16>,
        _account_type: AccountType,
        _end_time: Option<i64>,
    ) -> ExchangeResult<Vec<Kline>> {
        if interval != "1d" && interval != "1day" {
            return Err(ExchangeError::InvalidRequest(
                "KRX only provides daily (1d) candles".to_string(),
            ));
        }

        let sym_str: String = match symbol {
            SymbolInput::Raw(s) => s.to_string(),
            SymbolInput::Canonical(c) => c.to_concat(),
        };
        let code = sym_str.as_str();

        let n_days = limit.unwrap_or(30) as i64;

        use chrono::{Duration, Local, Datelike};
        let end = Local::now();
        let start = end - Duration::days(n_days - 1);

        let strt_dd = format_date(start.year(), start.month(), start.day());
        let end_dd = format_date(end.year(), end.month(), end.day());

        // Full ISIN = KR7 + 6-digit + 003 (best-effort; real ISIN may differ for some codes)
        let isin = format!("KR7{}003", code);

        let params: &[(&str, &str)] = &[
            ("bld", "dbms/MDC/STAT/standard/MDCSTAT01701"),
            ("isuCd", &isin),
            ("isuCd2", code),
            ("strtDd", &strt_dd),
            ("endDd", &end_dd),
            ("adjStkPrc", "2"),
            ("adjStkPrcTpCd", "S"),
        ];

        let response = self.post_data_marketplace(params).await?;
        let mut klines = KrxParser::parse_klines(&response, code)?;
        klines.sort_by_key(|k| k.open_time);
        Ok(klines)
    }

    /// Ping the API
    async fn ping(&self) -> ExchangeResult<()> {
        // Try to fetch today's data as ping
        let today = format_today();
        let body = serde_json::json!({
            "basDd": today
        });

        let _ = self.post_openapi(KrxEndpoint::KospiDailyTrading, body).await?;
        Ok(())
    }

    /// Get exchange info — returns KOSPI listed stocks from KRX
    async fn get_exchange_info(&self, account_type: AccountType) -> ExchangeResult<Vec<SymbolInfo>> {
        // KospiBaseInfo returns list of all listed securities on KOSPI
        let today = format_today();
        let body = serde_json::json!({
            "basDd": today
        });

        let response = self.post_openapi(KrxEndpoint::KospiBaseInfo, body).await?;

        // Iterate raw items directly so each SymbolInfo gets its native record in `extra`.
        // KRX Open API wraps items in "OutBlock_1", "data", or "items".
        let array = response
            .get("OutBlock_1")
            .or_else(|| response.get("data"))
            .or_else(|| response.get("items"))
            .and_then(|v| v.as_array())
            .ok_or_else(|| ExchangeError::Parse("Missing data array in KRX symbol list".to_string()))?;

        let infos = array.iter().filter_map(|item| {
            let code = item.get("ISU_SRT_CD")
                .or_else(|| item.get("srtnCd"))
                .or_else(|| item.get("ticker"))
                .and_then(|v| v.as_str())?.to_string();

            // KRX Open API has no per-symbol status field in the base info endpoint
            let status = String::new();

            // instrument_type: KRX does not expose a typed instrument class here
            let instrument_type: Option<String> = None;

            Some(SymbolInfo {
                symbol: code.clone(),
                base_asset: code,
                quote_asset: "KRW".to_string(),
                status,
                price_precision: 0,
                quantity_precision: 0,
                min_quantity: Some(1.0),
                max_quantity: None,
                tick_size: None,
                step_size: Some(1.0),
                min_notional: None,
                account_type,
                instrument_type,
                extra: item.clone(),
            })
        }).collect();

        Ok(infos)
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// TRAIT: Trading (NOT SUPPORTED - DATA PROVIDER ONLY)
// ═══════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl Trading for KrxConnector {
    async fn place_order(&self, _req: OrderRequest) -> ExchangeResult<PlaceOrderResponse> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - trading not supported".to_string()
        ))
    }

    async fn cancel_order(&self, _req: CancelRequest) -> ExchangeResult<Order> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - trading not supported".to_string()
        ))
    }

    async fn get_order(
        &self,
        _symbol: &str,
        _order_id: &str,
        _account_type: AccountType,
    ) -> ExchangeResult<Order> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - trading not supported".to_string()
        ))
    }

    async fn get_open_orders(
        &self,
        _symbol: Option<&str>,
        _account_type: AccountType,
    ) -> ExchangeResult<Vec<Order>> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - trading not supported".to_string()
        ))
    }

    async fn get_order_history(
        &self,
        _filter: OrderHistoryFilter,
        _account_type: AccountType,
    ) -> ExchangeResult<Vec<Order>> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - trading not supported".to_string()
        ))
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// TRAIT: Account (NOT SUPPORTED - DATA PROVIDER ONLY)
// ═══════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl Account for KrxConnector {
    async fn get_balance(&self, _query: BalanceQuery) -> ExchangeResult<Vec<Balance>> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - account operations not supported".to_string(),
        ))
    
    }

    async fn get_account_info(&self, _account_type: AccountType) -> ExchangeResult<AccountInfo> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - account operations not supported".to_string(),
        ))
    }

    async fn get_fees(&self, _symbol: Option<&str>) -> ExchangeResult<FeeInfo> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - account operations not supported".to_string()
        ))
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// TRAIT: Positions (NOT SUPPORTED - DATA PROVIDER ONLY)
// ═══════════════════════════════════════════════════════════════════════════

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl Positions for KrxConnector {
    async fn get_positions(&self, _query: PositionQuery) -> ExchangeResult<Vec<Position>> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - position tracking not supported".to_string()
        ))
    }

    async fn get_funding_rate(
        &self,
        _symbol: &str,
        _account_type: AccountType,
    ) -> ExchangeResult<FundingRate> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - position tracking not supported".to_string()
        ))
    }

    async fn modify_position(&self, _req: PositionModification) -> ExchangeResult<()> {
        Err(ExchangeError::NotImplemented(
            "KRX is a data provider - position tracking not supported".to_string()
        ))
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// EXTENDED METHODS (KRX-SPECIFIC)
// ═══════════════════════════════════════════════════════════════════════════

impl KrxConnector {
    /// Get stock information from Public Data Portal
    ///
    /// Returns detailed company information including name, market, ISIN, etc.
    pub async fn get_stock_info(&self, ticker: &str) -> ExchangeResult<serde_json::Value> {
        let mut params = HashMap::new();
        params.insert("likeSrtnCd".to_string(), ticker.to_string());

        let response = self.get_portal(params).await?;
        let items = KrxParser::parse_stock_info(&response)?;

        if let Some(first) = items.first() {
            Ok(first.clone())
        } else {
            Err(ExchangeError::NotFound(format!("Stock '{}' not found", ticker)))
        }
    }

    /// Get base info for a stock
    ///
    /// Uses the new Open API base info endpoint
    pub async fn get_base_info(
        &self,
        date: &str,
        market: MarketId,
    ) -> ExchangeResult<serde_json::Value> {
        let endpoint = match market {
            MarketId::Kospi => KrxEndpoint::KospiBaseInfo,
            MarketId::Kosdaq => KrxEndpoint::KosdaqBaseInfo,
            MarketId::Konex => KrxEndpoint::KonexBaseInfo,
            MarketId::All => KrxEndpoint::KospiBaseInfo,
        };

        let body = serde_json::json!({
            "basDd": date
        });

        self.post_openapi(endpoint, body).await
    }

    /// Get market index data
    pub async fn get_index_data(&self, date: &str) -> ExchangeResult<serde_json::Value> {
        let body = serde_json::json!({
            "basDd": date
        });

        self.post_openapi(KrxEndpoint::IndexDailyTrading, body).await
    }
}
