// src/corporate/storage.rs
use super::{types::*, helpers::*};
use crate::config;

use tokio::fs;
use chrono::{Datelike, NaiveDate};
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};

pub async fn load_existing_events() -> anyhow::Result<HashMap<String, CompanyEvent>> {
    let mut map = HashMap::new();
    let dir = std::path::Path::new("corporate_events");
    if !dir.exists() {
        return Ok(map);
    }

    let mut entries = fs::read_dir(dir).await?;
    while let Some(entry) = entries.next_entry().await? {
        let path = entry.path();
        if path.extension().and_then(|s| s.to_str()) == Some("json") {
            let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
            if name.starts_with("events_") && name.len() == 17 {
                let content = fs::read_to_string(&path).await?;
                let events: Vec<CompanyEvent> = serde_json::from_str(&content)?;
                for event in events {
                    map.insert(event_key(&event), event);
                }
            }
        }
    }
    Ok(map)
}

pub async fn save_optimized_events(events: HashMap<String, CompanyEvent>) -> anyhow::Result<()> {
    let dir = std::path::Path::new("corporate_events");
    fs::create_dir_all(dir).await?;

    let mut entries = fs::read_dir(dir).await?;
    while let Some(entry) = entries.next_entry().await? {
        let path = entry.path();
        let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
        if name.starts_with("events_") && path.extension().map(|e| e == "json").unwrap_or(false) {
            fs::remove_file(&path).await?;
        }
    }

    let mut sorted: Vec<_> = events.into_values().collect();
    sorted.sort_by_key(|e| (e.ticker.clone(), e.date.clone()));

    let mut by_month: HashMap<String, Vec<CompanyEvent>> = HashMap::new();
    for e in sorted {
        if let Ok(d) = NaiveDate::parse_from_str(&e.date, "%Y-%m-%d") {
            let key = format!("{}-{:02}", d.year(), d.month());
            by_month.entry(key).or_default().push(e);
        }
    }

    for (month, list) in by_month {
        let path = dir.join(format!("events_{}.json", month));
        fs::write(&path, serde_json::to_string_pretty(&list)?).await?;
    }
    Ok(())
}

pub async fn save_changes(changes: &[CompanyEventChange]) -> anyhow::Result<()> {
    if changes.is_empty() { return Ok(()); }
    let dir = std::path::Path::new("corporate_event_changes");
    fs::create_dir_all(dir).await?;

    let mut by_month: HashMap<String, Vec<CompanyEventChange>> = HashMap::new();
    for c in changes {
        if let Ok(d) = NaiveDate::parse_from_str(&c.date, "%Y-%m-%d") {
            let key = format!("{}-{:02}", d.year(), d.month());
            by_month.entry(key).or_default().push(c.clone());
        }
    }

    for (month, list) in by_month {
        let path = dir.join(format!("changes_{}.json", month));
        let mut all = if path.exists() {
            let s = fs::read_to_string(&path).await?;
            serde_json::from_str(&s).unwrap_or_default()
        } else { vec![] };
        all.extend(list);
        fs::write(&path, serde_json::to_string_pretty(&all)?).await?;
    }
    Ok(())
}

pub async fn save_prices_for_ticker(ticker: &str, timeframe: &str, mut prices: Vec<CompanyPrice>) -> anyhow::Result<()> {
    let base_dir = Path::new("corporate_prices");
    let company_dir = base_dir.join(ticker.replace(".", "_"));
    let timeframe_dir = company_dir.join(timeframe);

    fs::create_dir_all(&timeframe_dir).await?;
    let path = timeframe_dir.join("prices.json");

    prices.sort_by_key(|p| (p.date.clone(), p.time.clone()));

    let json = serde_json::to_string_pretty(&prices)?;
    fs::write(&path, json).await?;
    Ok(())
}

pub fn get_company_dir(lei: &str) -> PathBuf {
    PathBuf::from("corporate_prices").join(lei)
}

pub async fn ensure_company_dirs(isin: &str) -> anyhow::Result<()> {
    let base = get_company_dir(isin);
    let paths = [
        base.clone(),
        base.join("5min"),
        base.join("daily"),
        base.join("aggregated").join("5min"),
        base.join("aggregated").join("daily"),
    ];
    for p in paths {
        fs::create_dir_all(&p).await?;
    }
    Ok(())
}

pub async fn save_available_exchanges(isin: &str, exchanges: Vec<AvailableExchange>) -> anyhow::Result<()> {
    let dir = get_company_dir(isin);
    fs::create_dir_all(&dir).await?;
    let path = dir.join("available_exchanges.json");
    fs::write(&path, serde_json::to_string_pretty(&exchanges)?).await?;
    Ok(())
}

pub async fn load_available_exchanges(lei: &str) -> anyhow::Result<Vec<AvailableExchange>> {
    let path = get_company_dir(lei).join("available_exchanges.json");
    if path.exists() {
        let content = fs::read_to_string(&path).await?;
        Ok(serde_json::from_str(&content)?)
    } else {
        Ok(vec![])
    }
}

pub async fn save_prices_by_source(
    lei: &str,
    source_ticker: &str,
    timeframe: &str,
    prices: Vec<CompanyPrice>,
) -> anyhow::Result<()> {
    let source_safe = source_ticker.replace(".", "_").replace("/", "_");
    let dir = get_company_dir(lei).join(timeframe).join(&source_safe);
    fs::create_dir_all(&dir).await?;
    let path = dir.join("prices.json");
    let mut prices = prices;
    prices.sort_by_key(|p| (p.date.clone(), p.time.clone()));
    fs::write(&path, serde_json::to_string_pretty(&prices)?).await?;
    Ok(())
}

/// Update available_exchanges.json with fetch results
pub async fn update_available_exchange(
    isin: &str,
    ticker: &str,
    exchange_mic: &str,
    has_daily: bool,
    has_5min: bool,
) -> anyhow::Result<()> {
    let mut exchanges = load_available_exchanges(isin).await?;

    if let Some(entry) = exchanges.iter_mut().find(|e| e.ticker == ticker) {
        // Update existing entry
        entry.record_success(has_daily, has_5min);
    } else {
        // Create new entry - need to get currency from somewhere
        // Try to infer from the ticker or use a default
        let currency = infer_currency_from_ticker(ticker);
        let mut new_entry = AvailableExchange::new(
            ticker.to_string(),
            exchange_mic.to_string(),
            currency,
        );
        new_entry.record_success(has_daily, has_5min);
        exchanges.push(new_entry);
    }

    save_available_exchanges(isin, exchanges).await
}

/// Add a newly discovered exchange before fetching
/// 
/// # Arguments
/// * `isin` - The ISIN associated with the exchange.
/// * `figi_info` - The FigiInfo containing ticker, mic_code, and currency.
/// 
/// # Returns
/// Ok(()) on success.
/// 
/// # Errors
/// Returns an error if loading or saving available exchanges fails.
pub async fn add_discovered_exchange(
    isin: &str,
    figi_info: &FigiInfo,
) -> anyhow::Result<()> {
    let mut exchanges = load_available_exchanges(isin).await?;
    
    // Only add if not already present
    if !exchanges.iter().any(|e| e.ticker == figi_info.ticker && e.exchange_mic == figi_info.mic_code) {
        let new_entry = AvailableExchange::new(
            figi_info.ticker.clone(),
            figi_info.mic_code.clone(),
            figi_info.currency.clone(),
        );
        exchanges.push(new_entry);
        save_available_exchanges(isin, exchanges).await?;
    }
    
    Ok(())
}

/// Infer currency from ticker suffix
fn infer_currency_from_ticker(ticker: &str) -> String {
    if ticker.ends_with(".L") { return "GBP".to_string(); }
    if ticker.ends_with(".PA") { return "EUR".to_string(); }
    if ticker.ends_with(".DE") { return "EUR".to_string(); }
    if ticker.ends_with(".AS") { return "EUR".to_string(); }
    if ticker.ends_with(".MI") { return "EUR".to_string(); }
    if ticker.ends_with(".SW") { return "CHF".to_string(); }
    if ticker.ends_with(".T") { return "JPY".to_string(); }
    if ticker.ends_with(".HK") { return "HKD".to_string(); }
    if ticker.ends_with(".SS") { return "CNY".to_string(); }
    if ticker.ends_with(".SZ") { return "CNY".to_string(); }
    if ticker.ends_with(".TO") { return "CAD".to_string(); }
    if ticker.ends_with(".AX") { return "AUD".to_string(); }
    if ticker.ends_with(".SA") { return "BRL".to_string(); }
    if ticker.ends_with(".MC") { return "EUR".to_string(); }
    if ticker.ends_with(".BO") || ticker.ends_with(".NS") { return "INR".to_string(); }
    
    "USD".to_string() // Default
}