use std::fs;
use std::path::Path;

use anyhow::{Result, bail};

struct Reading {
    date: String,
    hc_bleu: u64,
    hp_bleu: u64,
    hc_blanc: u64,
    hp_blanc: u64,
    hc_rouge: u64,
    hp_rouge: u64,
}

struct Consumption {
    hc_bleu: u64,
    hp_bleu: u64,
    hc_blanc: u64,
    hp_blanc: u64,
    hc_rouge: u64,
    hp_rouge: u64,
}

impl Consumption {
    fn total(&self) -> u64 {
        self.hc_bleu + self.hp_bleu + self.hc_blanc + self.hp_blanc + self.hc_rouge + self.hp_rouge
    }

    fn total_hc(&self) -> u64 {
        self.hc_bleu + self.hc_blanc + self.hc_rouge
    }

    fn total_hp(&self) -> u64 {
        self.hp_bleu + self.hp_blanc + self.hp_rouge
    }
}

// Tarifs EDF 6 kVA (€/kWh et €/mois)
const BASE_ABO: f64 = 15.47;
const BASE_KWH: f64 = 0.1952;

const HPHC_ABO: f64 = 15.74;
const HPHC_HP: f64 = 0.2081;
const HPHC_HC: f64 = 0.1635;

const TEMPO_ABO: f64 = 15.50;
const TEMPO_BLEU_HC: f64 = 0.1232;
const TEMPO_BLEU_HP: f64 = 0.1494;
const TEMPO_BLANC_HC: f64 = 0.1391;
const TEMPO_BLANC_HP: f64 = 0.1730;
const TEMPO_ROUGE_HC: f64 = 0.1460;
const TEMPO_ROUGE_HP: f64 = 0.6468;

pub fn run(csv_path: &Path) -> Result<()> {
    let raw = fs::read(csv_path)?;
    let content = String::from_utf8(raw.clone())
        .unwrap_or_else(|_| raw.iter().map(|&b| b as char).collect());

    let readings = parse_readings(&content)?;

    if readings.len() < 2 {
        bail!("pas assez de relevés dans le fichier");
    }

    let last = readings.first().unwrap();

    // Chercher le relevé le plus proche de last.date - 1 an
    let target_date = one_year_before(&last.date);
    let start = readings
        .iter()
        .min_by_key(|r| date_distance(&r.date, &target_date))
        .unwrap();

    let conso = Consumption {
        hc_bleu: last.hc_bleu - start.hc_bleu,
        hp_bleu: last.hp_bleu - start.hp_bleu,
        hc_blanc: last.hc_blanc - start.hc_blanc,
        hp_blanc: last.hp_blanc - start.hp_blanc,
        hc_rouge: last.hc_rouge - start.hc_rouge,
        hp_rouge: last.hp_rouge - start.hp_rouge,
    };

    println!("Période : {} → {} ({} relevés)", start.date, last.date, readings.len());
    println!();

    print_consumption(&conso);
    println!();

    print_tariffs(&conso);

    Ok(())
}

fn parse_readings(content: &str) -> Result<Vec<Reading>> {
    let mut readings = Vec::new();

    for line in content.lines() {
        let parts: Vec<&str> = line.split(';').collect();
        if parts.len() != 8 {
            continue;
        }

        let Ok(hc_bleu) = parts[2].parse::<u64>() else {
            continue;
        };

        readings.push(Reading {
            date: parts[0].to_string(),
            hc_bleu,
            hp_bleu: parts[3].parse().unwrap_or(0),
            hc_blanc: parts[4].parse().unwrap_or(0),
            hp_blanc: parts[5].parse().unwrap_or(0),
            hc_rouge: parts[6].parse().unwrap_or(0),
            hp_rouge: parts[7].parse().unwrap_or(0),
        });
    }

    Ok(readings)
}

fn print_consumption(c: &Consumption) {
    let total = c.total() as f64;

    println!("Consommation sur la période :");
    println!();
    println!("  {:10} {:>8} {:>8}", "Type", "kWh", "%");
    println!("  {:10} {:>8} {:>8}", "──────────", "────────", "────────");
    print_conso_line("HC Bleu", c.hc_bleu, total);
    print_conso_line("HP Bleu", c.hp_bleu, total);
    print_conso_line("HC Blanc", c.hc_blanc, total);
    print_conso_line("HP Blanc", c.hp_blanc, total);
    print_conso_line("HC Rouge", c.hc_rouge, total);
    print_conso_line("HP Rouge", c.hp_rouge, total);
    println!("  {:10} {:>8} {:>8}", "──────────", "────────", "────────");
    println!("  {:10} {:>8}", "TOTAL", format_kwh(c.total()));
}

fn print_conso_line(label: &str, kwh: u64, total: f64) {
    let pct = format!("{:.1}", (kwh as f64 / total) * 100.0).replace('.', ",");
    println!("  {:10} {:>8} {:>7}%", label, format_kwh(kwh), pct);
}

fn print_tariffs(c: &Consumption) {
    let total = c.total();

    let base_abo = BASE_ABO * 12.0;
    let base_conso = total as f64 * BASE_KWH;
    let hphc_abo = HPHC_ABO * 12.0;
    let tempo_abo = TEMPO_ABO * 12.0;

    let (base_total, hphc_total, tempo_total) = compute_tariffs(c);

    println!("Comparatif des tarifs (6 kVA, 12 mois) :");
    println!();

    println!("  Base :");
    print_tariff_line("Abonnement", base_abo, 0, 0.0);
    print_tariff_line("Consommation", base_conso, total, BASE_KWH);
    println!("    {:20} {:>9} €", "Total", format_eur(base_total));
    println!();

    println!("  HP/HC :");
    print_tariff_line("Abonnement", hphc_abo, 0, 0.0);
    print_tariff_line("Heures Pleines", c.total_hp() as f64 * HPHC_HP, c.total_hp(), HPHC_HP);
    print_tariff_line("Heures Creuses", c.total_hc() as f64 * HPHC_HC, c.total_hc(), HPHC_HC);
    println!("    {:20} {:>9} €", "Total", format_eur(hphc_total));
    println!();

    println!("  Tempo :");
    print_tariff_line("Abonnement", tempo_abo, 0, 0.0);
    print_tariff_line("Bleu HC", c.hc_bleu as f64 * TEMPO_BLEU_HC, c.hc_bleu, TEMPO_BLEU_HC);
    print_tariff_line("Bleu HP", c.hp_bleu as f64 * TEMPO_BLEU_HP, c.hp_bleu, TEMPO_BLEU_HP);
    print_tariff_line("Blanc HC", c.hc_blanc as f64 * TEMPO_BLANC_HC, c.hc_blanc, TEMPO_BLANC_HC);
    print_tariff_line("Blanc HP", c.hp_blanc as f64 * TEMPO_BLANC_HP, c.hp_blanc, TEMPO_BLANC_HP);
    print_tariff_line("Rouge HC", c.hc_rouge as f64 * TEMPO_ROUGE_HC, c.hc_rouge, TEMPO_ROUGE_HC);
    print_tariff_line("Rouge HP", c.hp_rouge as f64 * TEMPO_ROUGE_HP, c.hp_rouge, TEMPO_ROUGE_HP);
    println!("    {:20} {:>9} €", "Total", format_eur(tempo_total));
    println!();

    // Résumé
    println!("Résumé :");
    println!();
    println!("  {:8} {:>12} {:>16}", "Option", "Coût annuel", "vs Base");
    println!("  {:8} {:>12} {:>16}", "────────", "────────────", "────────────────");

    let mut options = vec![
        ("Tempo", tempo_total),
        ("HP/HC", hphc_total),
        ("Base", base_total),
    ];
    options.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());

    for (name, cost) in &options {
        let diff = cost - base_total;
        let pct = (diff / base_total) * 100.0;
        if *name == "Base" {
            println!("  {:8} {:>10} €  référence", name, format_eur(*cost));
        } else {
            println!("  {:8} {:>10} €  {} € ({:+.0}%)", name, format_eur(*cost), format_eur(diff), pct);
        }
    }

    println!();
    println!("Coût moyen au kWh ({} kWh) :", format_kwh(total));
    println!();
    for (name, cost) in &options {
        println!("  {:8} {} cts/kWh", name, format_eur((cost / total as f64) * 100.0));
    }
}

fn print_tariff_line(label: &str, cost: f64, kwh: u64, rate: f64) {
    if kwh == 0 {
        println!("    {:20} {:>9} €", label, format_eur(cost));
    } else {
        println!("    {:20} {:>9} € ({:>5} kWh × {} €)", label, format_eur(cost), format_kwh(kwh), format_rate(rate));
    }
}

fn format_eur(value: f64) -> String {
    let abs = value.abs();
    let integer = abs as u64;
    let decimals = format!("{:.2}", abs.fract()).trim_start_matches('0').replace('.', ",");
    let sign = if value < 0.0 { "-" } else { "" };

    if integer >= 1000 {
        format!("{}{} {:03}{}", sign, integer / 1000, integer % 1000, decimals)
    } else {
        format!("{}{}{}", sign, integer, decimals)
    }
}

fn format_rate(value: f64) -> String {
    format!("{:.4}", value).replace('.', ",")
}

fn format_kwh(kwh: u64) -> String {
    if kwh >= 1000 {
        let thousands = kwh / 1000;
        let rest = kwh % 1000;
        format!("{} {:03}", thousands, rest)
    } else {
        kwh.to_string()
    }
}

// DD/MM/YYYY → (YYYY, MM, DD)
fn parse_date(date: &str) -> (i32, u32, u32) {
    let parts: Vec<&str> = date.split('/').collect();
    let day: u32 = parts[0].parse().unwrap();
    let month: u32 = parts[1].parse().unwrap();
    let year: i32 = parts[2].parse().unwrap();
    (year, month, day)
}

fn one_year_before(date: &str) -> String {
    let (year, month, day) = parse_date(date);
    format!("{:02}/{:02}/{}", day, month, year - 1)
}

fn date_distance(a: &str, b: &str) -> i32 {
    let (ya, ma, da) = parse_date(a);
    let (yb, mb, db) = parse_date(b);
    let days_a = ya * 365 + ma as i32 * 30 + da as i32;
    let days_b = yb * 365 + mb as i32 * 30 + db as i32;
    (days_a - days_b).abs()
}

fn compute_tariffs(c: &Consumption) -> (f64, f64, f64) {
    let total = c.total();

    let base_total = BASE_ABO * 12.0 + total as f64 * BASE_KWH;

    let hphc_total =
        HPHC_ABO * 12.0 + c.total_hp() as f64 * HPHC_HP + c.total_hc() as f64 * HPHC_HC;

    let tempo_total = TEMPO_ABO * 12.0
        + c.hc_bleu as f64 * TEMPO_BLEU_HC
        + c.hp_bleu as f64 * TEMPO_BLEU_HP
        + c.hc_blanc as f64 * TEMPO_BLANC_HC
        + c.hp_blanc as f64 * TEMPO_BLANC_HP
        + c.hc_rouge as f64 * TEMPO_ROUGE_HC
        + c.hp_rouge as f64 * TEMPO_ROUGE_HP;

    (base_total, hphc_total, tempo_total)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_and_tariffs() {
        let csv = "\
Récapitulatif\n\
\n\
Date;Type;HC Bleu;HP Bleu;HC Blanc;HP Blanc;HC Rouge;HP Rouge\n\
01/01/2025;Index;13000;6500;2800;1400;800;500\n\
15/06/2024;Index;11500;5700;2400;1200;650;400\n\
01/01/2024;Index;10000;5000;2000;1000;500;300\n";

        let readings = parse_readings(csv).unwrap();
        assert_eq!(readings.len(), 3);

        let last = readings.first().unwrap();
        let target_date = one_year_before(&last.date);
        let start = readings
            .iter()
            .min_by_key(|r| date_distance(&r.date, &target_date))
            .unwrap();

        assert_eq!(last.date, "01/01/2025");
        assert_eq!(start.date, "01/01/2024");

        let conso = Consumption {
            hc_bleu: last.hc_bleu - start.hc_bleu,
            hp_bleu: last.hp_bleu - start.hp_bleu,
            hc_blanc: last.hc_blanc - start.hc_blanc,
            hp_blanc: last.hp_blanc - start.hp_blanc,
            hc_rouge: last.hc_rouge - start.hc_rouge,
            hp_rouge: last.hp_rouge - start.hp_rouge,
        };

        assert_eq!(conso.hc_bleu, 3000);
        assert_eq!(conso.hp_bleu, 1500);
        assert_eq!(conso.hc_blanc, 800);
        assert_eq!(conso.hp_blanc, 400);
        assert_eq!(conso.hc_rouge, 300);
        assert_eq!(conso.hp_rouge, 200);
        assert_eq!(conso.total(), 6200);

        let (base, hphc, tempo) = compute_tariffs(&conso);

        assert!((base - 1395.88).abs() < 0.01);
        assert!((hphc - 1296.24).abs() < 0.01);
        assert!((tempo - 1133.34).abs() < 0.01);
    }

    #[test]
    fn test_utf8_parsing() {
        let csv = "Récapitulatif\n\nDate;Type;HC Bleu;HP Bleu;HC Blanc;HP Blanc;HC Rouge;HP Rouge\n01/01/2024;Index;1000;500;100;50;10;5\n01/01/2025;Index;2000;800;200;100;30;15\n";
        let readings = parse_readings(csv).unwrap();

        assert_eq!(readings.len(), 2);
        assert_eq!(readings[0].hc_bleu, 1000);
        assert_eq!(readings[1].hc_bleu, 2000);
    }
}
