use chrono::{DateTime, TimeDelta, Utc};
use chrono_tz::Tz;
use itertools::Itertools;
use std::{collections::HashMap, marker::PhantomData};

use crate::{
    CostPeriod, CostPeriodMatching, CostPeriods, TariffCalculationMethod,
    power_tariffs::PowerDivide,
};

/// Matching power tariff peaks for a given set of power averages
#[derive(Clone, Debug)]
pub struct PowerTariffMatches {
    calc_method: TariffCalculationMethod,
    cost_period_matching: CostPeriodMatching,
    power_divide: Option<PowerDivide>,
    items: Vec<PeriodPeakMatches>,
    current_power_average: Option<PartialPowerAverage>,
}

impl PowerTariffMatches {
    pub fn new(
        calc_method: TariffCalculationMethod,
        power_divide: Option<PowerDivide>,
        periods: CostPeriods,
        averages: &[PowerAverage<Virtual>],
        current_power_average: Option<PartialPowerAverage>,
    ) -> Self {
        let cost_period_matching = periods.match_method();
        let items = PeriodPeakMatches::new(calc_method, &periods, averages, cost_period_matching);

        Self {
            calc_method,
            cost_period_matching,
            power_divide,
            items,
            current_power_average,
        }
    }

    pub fn new_dummy() -> Self {
        Self::new(
            TariffCalculationMethod::AverageDays(99),
            None,
            CostPeriods::new_first(&[]),
            &[],
            None,
        )
    }

    pub fn calc_method(&self) -> TariffCalculationMethod {
        self.calc_method
    }

    pub fn cost_period_matching(&self) -> CostPeriodMatching {
        self.cost_period_matching
    }

    pub fn items(&self) -> &[PeriodPeakMatches] {
        &self.items
    }

    pub fn current_power_average(&self) -> Option<PartialPowerAverage> {
        self.current_power_average
    }

    pub fn power_divide(&self) -> Option<PowerDivide> {
        self.power_divide
    }
}

/// Power tariff peaks that match the given cost period
#[derive(Clone, Debug)]
pub struct PeriodPeakMatches {
    period: CostPeriod,
    peaks: PowerPeaks,
}

impl PeriodPeakMatches {
    pub fn period(&self) -> &CostPeriod {
        &self.period
    }
    pub fn peaks(&self) -> &PowerPeaks {
        &self.peaks
    }

    fn new(
        calc_method: TariffCalculationMethod,
        periods: &CostPeriods,
        averages: &[PowerAverage<Virtual>],
        match_method: CostPeriodMatching,
    ) -> Vec<Self> {
        let mut used_indices = if match_method == CostPeriodMatching::First {
            Some(std::collections::HashSet::new())
        } else {
            None
        };

        periods
            .iter()
            .map(|period| {
                let averages_for_period: Vec<PowerAverage<Virtual>> = averages
                    .iter()
                    .enumerate()
                    .filter_map(|(avg_idx, avg)| {
                        if period.matches(avg.timestamp) {
                            // For First matching, skip if already used
                            if let Some(ref mut used) = used_indices {
                                if used.contains(&avg_idx) {
                                    return None;
                                }
                                used.insert(avg_idx);
                            }
                            Some(*avg)
                        } else {
                            None
                        }
                    })
                    .collect();

                Self {
                    period: period.clone(),
                    peaks: PowerPeaks::new(calc_method, &averages_for_period),
                }
            })
            .collect()
    }
}

/// Power average that is not deemed complete
#[derive(Copy, Clone, Debug)]
pub struct PartialPowerAverage {
    power_average: PowerAverage<Actual>,
    duration_secs: u16,
}

impl PartialPowerAverage {
    pub fn new(power_average: PowerAverage<Actual>, duration_secs: u16) -> Self {
        Self {
            power_average,
            duration_secs,
        }
    }

    pub fn power_average(&self) -> PowerAverage<Actual> {
        self.power_average
    }

    pub fn duration_secs(&self) -> u16 {
        self.duration_secs
    }

    fn start(&self) -> DateTime<Tz> {
        self.power_average().timestamp()
    }

    fn end(&self) -> DateTime<Tz> {
        self.power_average().timestamp() + TimeDelta::seconds(self.duration_secs().into())
    }

    pub fn covers(&self, ts: DateTime<Utc>, secs: u16) -> bool {
        let ts_end = ts + TimeDelta::seconds(secs.into());
        ts >= self.start() && ts_end <= self.end()
    }

    /// Calculates what percentage of a given time range is covered by this partial power average.
    ///
    /// This method computes the overlap between the partial power average's time range
    /// `[self.start(), self.end()]` and the query time range `[ts, ts + num_secs]`.
    ///
    /// # Arguments
    ///
    /// * `ts` - The start timestamp of the query range (in UTC)
    /// * `num_secs` - The duration of the query range in seconds
    ///
    /// # Returns
    ///
    /// A percentage (0-100) representing how much of the query range is covered by this
    /// partial power average. Returns 0 if there is no overlap.
    ///
    /// # Examples
    ///
    /// ```
    /// // If partial average covers 10:00-11:00 (3600 seconds)
    /// // and query is 10:15-10:45 (1800 seconds)
    /// // Result is 100% (query range completely within partial average)
    ///
    /// // If partial average covers 10:00-11:00
    /// // and query is 10:30-11:30 (3600 seconds)
    /// // Result is 50% (1800 seconds overlap out of 3600 seconds query)
    /// ```
    pub fn cover_percentage(&self, ts: DateTime<Utc>, num_secs: u32) -> u8 {
        let ts_end = ts + TimeDelta::seconds(num_secs.into());

        // Convert to UTC for comparison
        let self_start = self.start().with_timezone(&Utc);
        let self_end = self.end().with_timezone(&Utc);

        // Calculate the overlap between [ts, ts_end] and [self.start(), self.end()]
        let overlap_start = ts.max(self_start);
        let overlap_end = ts_end.min(self_end);

        // If there's no overlap, return 0
        if overlap_start >= overlap_end {
            return 0;
        }

        // Calculate the number of seconds of overlap
        let overlap_secs = (overlap_end - overlap_start).num_seconds();

        // Calculate and return the percentage covered
        (overlap_secs as f64 / num_secs as f64 * 100.0) as u8
    }
}

pub trait PowerAverageType {}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Virtual;
impl PowerAverageType for Virtual {}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Actual;
impl PowerAverageType for Actual {}

/// Average of power measurements for a certain period of time
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct PowerAverage<T: PowerAverageType> {
    timestamp: DateTime<Tz>,
    value: u32,
    _type: PhantomData<T>,
}

impl<T: PowerAverageType> PowerAverage<T> {
    pub fn timestamp(&self) -> DateTime<Tz> {
        self.timestamp
    }

    pub fn kw(&self) -> f64 {
        self.value as f64 / 1000.
    }
}

impl PowerAverage<Actual> {
    pub fn new<Dt: Into<DateTime<Tz>>>(timestamp: Dt, value: u32) -> Self {
        Self {
            timestamp: timestamp.into(),
            value,
            _type: PhantomData,
        }
    }

    pub fn into_virtual(self, power_divide: Option<PowerDivide>) -> PowerAverage<Virtual> {
        let adjusted_value = if let Some(pd) = power_divide {
            (self.value as f64 * pd.multiplier(self.timestamp)) as u32
        } else {
            self.value
        };

        PowerAverage {
            timestamp: self.timestamp,
            value: adjusted_value,
            _type: PhantomData,
        }
    }
}

/// Observed power peaks
#[derive(Default, Clone, Debug)]
pub struct PowerPeaks(Vec<PowerAverage<Virtual>>);

impl PowerPeaks {
    pub fn new(
        calc_method: TariffCalculationMethod,
        period_averages: &[PowerAverage<Virtual>],
    ) -> Self {
        let peaks: Vec<PowerAverage<Virtual>> = match calc_method {
            crate::TariffCalculationMethod::AverageDays(n) => {
                // For AverageDays: get the highest hour from each of the top n days
                let mut daily_peaks: HashMap<chrono::NaiveDate, PowerAverage<Virtual>> =
                    HashMap::new();

                // Group by day and keep only the highest value for each day
                for power_average in period_averages {
                    let date = power_average.timestamp.date_naive();
                    daily_peaks
                        .entry(date)
                        .and_modify(|existing| {
                            if power_average.value > existing.value {
                                *existing = *power_average;
                            }
                        })
                        .or_insert(*power_average);
                }

                // Convert to vector and sort by value descending
                daily_peaks
                    .into_values()
                    .sorted_by_key(|power_average| power_average.value)
                    .rev()
                    .take(n as usize)
                    .collect()
            }
            crate::TariffCalculationMethod::AverageHours(n) => {
                // For AverageHours: get the top n hours by value
                period_averages
                    .iter()
                    .sorted_by_key(|power_average| power_average.value)
                    .rev()
                    .take(n as usize)
                    .copied()
                    .collect()
            }
        };

        Self(peaks)
    }

    pub fn values(&self) -> &[PowerAverage<Virtual>] {
        &self.0
    }

    pub fn min(&self) -> Option<PowerAverage<Virtual>> {
        self.values().last().copied()
    }

    pub fn max(&self) -> Option<PowerAverage<Virtual>> {
        self.values().first().copied()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Country, LoadType, Stockholm, months::Month};
    use chrono::{Datelike, Timelike};

    #[test]
    fn average_hours_returns_n_highest_values() {
        let averages = vec![
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 0, 0, 0), 100),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 1, 0, 0), 500),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 2, 0, 0), 300),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 3, 0, 0), 200),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 4, 0, 0), 400),
        ];

        let peaks = PowerPeaks::new(
            TariffCalculationMethod::AverageHours(3),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );

        assert_eq!(peaks.values().len(), 3);
        assert_eq!(peaks.values()[0].value, 500);
        assert_eq!(peaks.values()[1].value, 400);
        assert_eq!(peaks.values()[2].value, 300);
    }

    #[test]
    fn average_hours_with_equal_values() {
        let averages = vec![
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 0, 0, 0), 500),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 1, 0, 0), 500),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 2, 0, 0), 300),
        ];

        let peaks = PowerPeaks::new(
            TariffCalculationMethod::AverageHours(2),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );

        assert_eq!(peaks.values().len(), 2);
        assert_eq!(peaks.values()[0].value, 500);
        assert_eq!(peaks.values()[1].value, 500);
    }

    #[test]
    fn average_hours_empty_input() {
        let averages = vec![];

        let peaks = PowerPeaks::new(TariffCalculationMethod::AverageHours(3), &averages);

        assert_eq!(peaks.values().len(), 0);
    }

    #[test]
    fn average_hours_zero_n() {
        let averages = vec![PowerAverage::new(Stockholm.dt(2025, 1, 1, 0, 0, 0), 100)];

        let peaks = PowerPeaks::new(
            TariffCalculationMethod::AverageHours(0),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );

        assert_eq!(peaks.values().len(), 0);
    }

    #[test]
    fn average_hours_n_greater_than_available() {
        let averages = vec![
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 0, 0, 0), 100),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 1, 0, 0), 200),
        ];

        let peaks = PowerPeaks::new(
            TariffCalculationMethod::AverageHours(5),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );

        assert_eq!(peaks.values().len(), 2);
        assert_eq!(peaks.values()[0].value, 200);
        assert_eq!(peaks.values()[1].value, 100);
    }

    #[test]
    fn average_days_one_peak_per_day() {
        let averages = vec![
            // Day 1: peak at 500
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 11, 0, 0), 500),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 12, 0, 0), 300),
            // Day 2: peak at 600
            PowerAverage::new(Stockholm.dt(2025, 1, 2, 10, 0, 0), 200),
            PowerAverage::new(Stockholm.dt(2025, 1, 2, 11, 0, 0), 600),
            // Day 3: peak at 250
            PowerAverage::new(Stockholm.dt(2025, 1, 3, 10, 0, 0), 150),
            PowerAverage::new(Stockholm.dt(2025, 1, 3, 11, 0, 0), 250),
        ];

        let peaks = PowerPeaks::new(
            TariffCalculationMethod::AverageDays(2),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );

        assert_eq!(peaks.values().len(), 2);
        assert_eq!(peaks.values()[0].value, 600);
        assert_eq!(peaks.values()[0].timestamp.day(), 2);
        assert_eq!(peaks.values()[1].value, 500);
        assert_eq!(peaks.values()[1].timestamp.day(), 1);
    }

    #[test]
    fn average_days_ensures_different_days() {
        let averages = vec![
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 500),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 11, 0, 0), 450),
            PowerAverage::new(Stockholm.dt(2025, 1, 2, 10, 0, 0), 300),
        ];

        let peaks = PowerPeaks::new(
            TariffCalculationMethod::AverageDays(2),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );

        assert_eq!(peaks.values().len(), 2);
        let day1 = peaks.values()[0].timestamp.date_naive();
        let day2 = peaks.values()[1].timestamp.date_naive();
        assert_ne!(day1, day2);
    }

    #[test]
    fn average_days_preserves_peak_hour_timestamp() {
        let averages = vec![
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 14, 0, 0), 500),
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 20, 0, 0), 300),
        ];

        let peaks = PowerPeaks::new(
            TariffCalculationMethod::AverageDays(1),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );

        assert_eq!(peaks.values().len(), 1);
        assert_eq!(peaks.values()[0].timestamp.hour(), 14);
        assert_eq!(peaks.values()[0].value, 500);
    }

    #[test]
    fn average_days_empty_input() {
        let averages = vec![];

        let peaks = PowerPeaks::new(TariffCalculationMethod::AverageDays(3), &averages);

        assert_eq!(peaks.values().len(), 0);
    }

    #[test]
    fn average_days_zero_n() {
        let averages = vec![PowerAverage::new(Stockholm.dt(2025, 1, 1, 0, 0, 0), 100)];

        let peaks = PowerPeaks::new(
            TariffCalculationMethod::AverageDays(0),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );

        assert_eq!(peaks.values().len(), 0);
    }

    #[test]
    fn average_days_n_greater_than_available_days() {
        let averages = vec![
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            PowerAverage::new(Stockholm.dt(2025, 1, 2, 10, 0, 0), 200),
        ];

        let peaks = PowerPeaks::new(
            TariffCalculationMethod::AverageDays(5),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );

        assert_eq!(peaks.values().len(), 2);
    }

    #[test]
    fn peak_periods_first_matching_splits_values() {
        static PERIODS_ARRAY: [CostPeriod; 2] = [
            CostPeriod::builder()
                .load(LoadType::High)
                .fixed_cost(10, 0)
                .hours(6, 22)
                .months(Month::November, Month::March)
                .exclude_weekends()
                .exclude_holidays(Country::SE)
                .build(),
            CostPeriod::builder()
                .load(LoadType::Low)
                .fixed_cost(5, 0)
                .build(),
        ];
        let periods = CostPeriods::new_first(&PERIODS_ARRAY);

        // January 15, 2025 is a Wednesday
        let averages = vec![
            // Matches high: winter weekday 10:00
            PowerAverage::new(Stockholm.dt(2025, 1, 15, 10, 0, 0), 500),
            // Doesn't match high (hour 23): goes to low
            PowerAverage::new(Stockholm.dt(2025, 1, 15, 23, 0, 0), 300),
            // Matches high: winter weekday 14:00
            PowerAverage::new(Stockholm.dt(2025, 1, 15, 14, 0, 0), 400),
        ];

        let result = PowerTariffMatches::new(
            TariffCalculationMethod::AverageHours(10),
            None,
            periods,
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
            None,
        );

        assert_eq!(result.items().len(), 2);

        // High period gets 2 values (10:00, 14:00)
        assert_eq!(result.items()[0].peaks().values().len(), 2);
        assert_eq!(result.items()[0].peaks().values()[0].value, 500);
        assert_eq!(result.items()[0].peaks().values()[1].value, 400);

        // Low period gets 1 value (23:00)
        assert_eq!(result.items()[1].peaks().values().len(), 1);
        assert_eq!(result.items()[1].peaks().values()[0].value, 300);
    }

    #[test]
    fn peak_periods_all_matching_duplicates_values() {
        static PERIODS_ARRAY: [CostPeriod; 2] = [
            CostPeriod::builder()
                .load(LoadType::High)
                .fixed_cost(10, 0)
                .hours(6, 22)
                .months(Month::November, Month::March)
                .exclude_weekends()
                .exclude_holidays(Country::SE)
                .build(),
            CostPeriod::builder()
                .load(LoadType::Low)
                .fixed_cost(5, 0)
                .build(),
        ];
        let periods = CostPeriods::new_all(&PERIODS_ARRAY);

        // January 15, 2025 is a Wednesday
        let averages = vec![
            PowerAverage::new(Stockholm.dt(2025, 1, 15, 10, 0, 0), 500),
            PowerAverage::new(Stockholm.dt(2025, 1, 15, 23, 0, 0), 300),
        ];

        let result = PowerTariffMatches::new(
            TariffCalculationMethod::AverageHours(10),
            None,
            periods,
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
            None,
        );

        assert_eq!(result.items().len(), 2);

        // High period gets 1 value (only 10:00 matches criteria)
        assert_eq!(result.items()[0].peaks().values().len(), 1);
        assert_eq!(result.items()[0].peaks().values()[0].value, 500);

        // Low period gets both (no restrictions)
        assert_eq!(result.items()[1].peaks().values().len(), 2);
        assert_eq!(result.items()[1].peaks().values()[0].value, 500);
        assert_eq!(result.items()[1].peaks().values()[1].value, 300);
    }

    #[test]
    fn peak_periods_empty_averages() {
        static PERIODS_ARRAY: [CostPeriod; 1] = [CostPeriod::builder()
            .load(LoadType::Low)
            .fixed_cost(5, 0)
            .build()];
        let periods = CostPeriods::new_first(&PERIODS_ARRAY);
        let averages = vec![];

        let result = PowerTariffMatches::new(
            TariffCalculationMethod::AverageHours(3),
            None,
            periods,
            &averages,
            None,
        );

        assert_eq!(result.items().len(), 1);
        assert_eq!(result.items()[0].peaks().values().len(), 0);
    }

    #[test]
    fn single_value_both_methods() {
        let averages = vec![PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100)];

        let hours = PowerPeaks::new(
            TariffCalculationMethod::AverageHours(3),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );
        assert_eq!(hours.values().len(), 1);
        assert_eq!(hours.values()[0].value, 100);

        let days = PowerPeaks::new(
            TariffCalculationMethod::AverageDays(3),
            &averages
                .iter()
                .map(|a| a.into_virtual(None))
                .collect::<Vec<_>>(),
        );
        assert_eq!(days.values().len(), 1);
        assert_eq!(days.values()[0].value, 100);
    }

    #[test]
    fn covers_percentage_complete_overlap() {
        // Partial average covers 10:00-11:00 (3600 seconds)
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query range is completely within the partial average range
        let ts = Stockholm.dt(2025, 1, 1, 10, 15, 0).with_timezone(&Utc); // 10:15
        let result = partial.cover_percentage(ts, 1800); // 30 minutes

        assert_eq!(result, 100); // 100% overlap
    }

    #[test]
    fn covers_percentage_partial_overlap_start() {
        // Partial average covers 10:00-11:00
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query range starts before partial average, ends within it
        // 09:30-10:30 overlaps with 10:00-11:00 at 10:00-10:30 (1800 seconds)
        let ts = Stockholm.dt(2025, 1, 1, 9, 30, 0).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 3600); // 60 minutes

        assert_eq!(result, 50); // 1800/3600 = 50%
    }

    #[test]
    fn covers_percentage_partial_overlap_end() {
        // Partial average covers 10:00-11:00
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query range starts within partial average, ends after it
        // 10:30-11:30 overlaps with 10:00-11:00 at 10:30-11:00 (1800 seconds)
        let ts = Stockholm.dt(2025, 1, 1, 10, 30, 0).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 3600); // 60 minutes

        assert_eq!(result, 50); // 1800/3600 = 50%
    }

    #[test]
    fn covers_percentage_no_overlap_before() {
        // Partial average covers 10:00-11:00
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query range is completely before the partial average
        let ts = Stockholm.dt(2025, 1, 1, 8, 0, 0).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 3600);

        assert_eq!(result, 0);
    }

    #[test]
    fn covers_percentage_no_overlap_after() {
        // Partial average covers 10:00-11:00
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query range is completely after the partial average
        let ts = Stockholm.dt(2025, 1, 1, 12, 0, 0).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 3600);

        assert_eq!(result, 0);
    }

    #[test]
    fn covers_percentage_no_overlap() {
        // Partial average covers 10:00-11:00 Stockholm (09:00-10:00 UTC)
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query is 09:00-10:00 Stockholm (08:00-09:00 UTC)
        // This overlaps with 09:00-10:00 UTC by 0 seconds
        let ts = Stockholm.dt(2025, 1, 1, 9, 0, 0).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 3600);

        assert_eq!(result, 0);
    }

    #[test]
    fn covers_percentage_half_overlap() {
        // Partial average covers 10:00-11:00
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query overlaps by 1800 seconds (30 minutes) out of 3600 (60 minutes) = 50%
        let ts = Stockholm.dt(2025, 1, 1, 9, 30, 0).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 3600);

        assert_eq!(result, 50);
    }

    #[test]
    fn covers_percentage_query_contains_partial() {
        // Partial average covers 10:00-11:00 (3600 seconds)
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query range completely contains the partial average
        // 09:00-12:00 (10800 seconds) contains 10:00-11:00
        let ts = Stockholm.dt(2025, 1, 1, 9, 0, 0).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 10800);

        assert_eq!(result, 33); // 3600/10800 = 33.33% -> 33 as u8
    }

    #[test]
    fn covers_percentage_exact_match() {
        // Partial average covers 10:00-11:00
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query range exactly matches the partial average range
        let ts = Stockholm.dt(2025, 1, 1, 10, 0, 0).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 3600);

        assert_eq!(result, 100);
    }

    #[test]
    fn covers_percentage_adjacent_ranges_no_overlap() {
        // Partial average covers 10:00-11:00
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query range starts exactly when partial average ends
        let ts = Stockholm.dt(2025, 1, 1, 11, 0, 0).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 3600);

        assert_eq!(result, 0); // Adjacent but not overlapping
    }

    #[test]
    fn covers_percentage_very_small_overlap() {
        // Partial average covers 10:00-11:00
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            3600,
        );

        // Query overlaps by just 1 second
        let ts = Stockholm.dt(2025, 1, 1, 10, 59, 59).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 3600);

        // 1 second out of 3600 = 0.027% -> 0 as u8
        assert_eq!(result, 0);
    }

    #[test]
    fn covers_percentage_short_duration() {
        // Partial average covers 10:00:00-10:01:00 (60 seconds)
        let partial = PartialPowerAverage::new(
            PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
            60,
        );

        // Query overlaps by 30 seconds
        let ts = Stockholm.dt(2025, 1, 1, 10, 0, 30).with_timezone(&Utc);
        let result = partial.cover_percentage(ts, 60);

        assert_eq!(result, 50); // 30/60 = 50%
    }

    mod map_periods_to_data_tests {
        use super::*;
        use crate::{LoadType, Stockholm, months::Month};

        #[test]
        fn map_periods_first_matching_no_overlap() {
            // Create periods where averages match different periods
            static PERIODS_ARRAY: [CostPeriod; 2] = [
                CostPeriod::builder()
                    .load(LoadType::High)
                    .fixed_cost(10, 0)
                    .hours(6, 12)
                    .build(),
                CostPeriod::builder()
                    .load(LoadType::Low)
                    .fixed_cost(5, 0)
                    .hours(12, 22)
                    .build(),
            ];
            let periods = CostPeriods::new_first(&PERIODS_ARRAY);

            let averages = vec![
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 10, 0, 0), 500), // High period
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 14, 0, 0), 400), // Low period
            ];

            let result = PeriodPeakMatches::new(
                TariffCalculationMethod::AverageHours(10),
                &periods,
                &averages
                    .iter()
                    .map(|a| a.into_virtual(None))
                    .collect::<Vec<_>>(),
                CostPeriodMatching::First,
            );

            assert_eq!(result.len(), 2);
            assert_eq!(result[0].peaks().values().len(), 1);
            assert_eq!(result[0].peaks().values()[0].value, 500);
            assert_eq!(result[1].peaks().values().len(), 1);
            assert_eq!(result[1].peaks().values()[0].value, 400);
        }

        #[test]
        fn map_periods_first_matching_overlapping_periods() {
            // Create overlapping periods where First matching matters
            static PERIODS_ARRAY: [CostPeriod; 2] = [
                CostPeriod::builder()
                    .load(LoadType::High)
                    .fixed_cost(10, 0)
                    .hours(6, 18) // 6-18
                    .build(),
                CostPeriod::builder()
                    .load(LoadType::Low)
                    .fixed_cost(5, 0)
                    .hours(12, 22) // 12-22, overlaps with high
                    .build(),
            ];
            let periods = CostPeriods::new_first(&PERIODS_ARRAY);

            let averages = vec![
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 10, 0, 0), 500), // Only high
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 14, 0, 0), 400), // Both match, goes to high (first)
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 20, 0, 0), 300), // Only low
            ];

            let result = PeriodPeakMatches::new(
                TariffCalculationMethod::AverageHours(10),
                &periods,
                &averages
                    .iter()
                    .map(|a| a.into_virtual(None))
                    .collect::<Vec<_>>(),
                CostPeriodMatching::First,
            );

            assert_eq!(result.len(), 2);
            // High gets first two (10:00 and 14:00)
            assert_eq!(result[0].peaks().values().len(), 2);
            assert_eq!(result[0].peaks().values()[0].value, 500);
            assert_eq!(result[0].peaks().values()[1].value, 400);
            // Low gets only the last one (20:00)
            assert_eq!(result[1].peaks().values().len(), 1);
            assert_eq!(result[1].peaks().values()[0].value, 300);
        }

        #[test]
        fn map_periods_all_matching_duplicates() {
            // Create overlapping periods where All matching allows duplicates
            static PERIODS_ARRAY: [CostPeriod; 2] = [
                CostPeriod::builder()
                    .load(LoadType::High)
                    .fixed_cost(10, 0)
                    .hours(6, 18)
                    .build(),
                CostPeriod::builder()
                    .load(LoadType::Base)
                    .fixed_cost(5, 0)
                    .build(),
            ];
            let periods = CostPeriods::new_all(&PERIODS_ARRAY);

            let averages = vec![
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 14, 0, 0), 400), // Matches both
            ];

            let result = PeriodPeakMatches::new(
                TariffCalculationMethod::AverageHours(10),
                &periods,
                &averages
                    .iter()
                    .map(|a| a.into_virtual(None))
                    .collect::<Vec<_>>(),
                CostPeriodMatching::All,
            );

            assert_eq!(result.len(), 2);
            // Both periods get the same average
            assert_eq!(result[0].peaks().values().len(), 1);
            assert_eq!(result[0].peaks().values()[0].value, 400);
            assert_eq!(result[1].peaks().values().len(), 1);
            assert_eq!(result[1].peaks().values()[0].value, 400);
        }

        #[test]
        fn map_periods_empty_averages() {
            static PERIODS_ARRAY: [CostPeriod; 1] = [CostPeriod::builder()
                .load(LoadType::Low)
                .fixed_cost(5, 0)
                .build()];
            let periods = CostPeriods::new_first(&PERIODS_ARRAY);
            let averages = vec![];

            let result = PeriodPeakMatches::new(
                TariffCalculationMethod::AverageHours(3),
                &periods,
                &averages,
                CostPeriodMatching::First,
            );

            assert_eq!(result.len(), 1);
            assert_eq!(result[0].peaks().values().len(), 0);
        }

        #[test]
        fn map_periods_no_matching_averages() {
            // Create a period that doesn't match any averages
            static PERIODS_ARRAY: [CostPeriod; 1] = [CostPeriod::builder()
                .load(LoadType::High)
                .fixed_cost(10, 0)
                .hours(6, 12)
                .months(Month::June, Month::August) // Summer only
                .build()];
            let periods = CostPeriods::new_first(&PERIODS_ARRAY);

            // January averages don't match summer period
            let averages = vec![PowerAverage::new(Stockholm.dt(2025, 1, 15, 10, 0, 0), 500)];

            let result = PeriodPeakMatches::new(
                TariffCalculationMethod::AverageHours(10),
                &periods,
                &averages
                    .iter()
                    .map(|a| a.into_virtual(None))
                    .collect::<Vec<_>>(),
                CostPeriodMatching::First,
            );

            assert_eq!(result.len(), 1);
            assert_eq!(result[0].peaks().values().len(), 0);
        }

        #[test]
        fn map_periods_preserves_calculation_method() {
            static PERIODS_ARRAY: [CostPeriod; 1] = [CostPeriod::builder()
                .load(LoadType::High)
                .fixed_cost(10, 0)
                .build()];
            let periods = CostPeriods::new_first(&PERIODS_ARRAY);

            // Create multiple averages on same day and different days
            let averages = vec![
                PowerAverage::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 500),
                PowerAverage::new(Stockholm.dt(2025, 1, 1, 14, 0, 0), 600),
                PowerAverage::new(Stockholm.dt(2025, 1, 2, 10, 0, 0), 300),
                PowerAverage::new(Stockholm.dt(2025, 1, 2, 14, 0, 0), 400),
            ];

            // Test with AverageDays(1) - should get 1 peak (highest from top day)
            let result_days = PeriodPeakMatches::new(
                TariffCalculationMethod::AverageDays(1),
                &periods,
                &averages
                    .iter()
                    .map(|a| a.into_virtual(None))
                    .collect::<Vec<_>>(),
                CostPeriodMatching::First,
            );
            assert_eq!(result_days[0].peaks().values().len(), 1);
            assert_eq!(result_days[0].peaks().values()[0].value, 600); // Highest from day 1

            // Test with AverageHours(2) - should get 2 peaks (2 highest hours)
            let result_hours = PeriodPeakMatches::new(
                TariffCalculationMethod::AverageHours(2),
                &periods,
                &averages
                    .iter()
                    .map(|a| a.into_virtual(None))
                    .collect::<Vec<_>>(),
                CostPeriodMatching::First,
            );
            assert_eq!(result_hours[0].peaks().values().len(), 2);
            assert_eq!(result_hours[0].peaks().values()[0].value, 600);
            assert_eq!(result_hours[0].peaks().values()[1].value, 500);
        }

        #[test]
        fn map_periods_first_matching_multiple_periods() {
            // Test with 3 periods to ensure First matching works correctly across multiple
            static PERIODS_ARRAY: [CostPeriod; 3] = [
                CostPeriod::builder()
                    .load(LoadType::High)
                    .fixed_cost(10, 0)
                    .hours(6, 12)
                    .build(),
                CostPeriod::builder()
                    .load(LoadType::Base)
                    .fixed_cost(7, 50)
                    .hours(12, 18)
                    .build(),
                CostPeriod::builder()
                    .load(LoadType::Low)
                    .fixed_cost(5, 0)
                    .build(),
            ];
            let periods = CostPeriods::new_first(&PERIODS_ARRAY);

            let averages = vec![
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 10, 0, 0), 500), // High
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 14, 0, 0), 400), // Base
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 20, 0, 0), 300), // Low
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 3, 0, 0), 200),  // Low
            ];

            let result = PeriodPeakMatches::new(
                TariffCalculationMethod::AverageHours(10),
                &periods,
                &averages
                    .iter()
                    .map(|a| a.into_virtual(None))
                    .collect::<Vec<_>>(),
                CostPeriodMatching::First,
            );

            assert_eq!(result.len(), 3);
            assert_eq!(result[0].peaks().values().len(), 1); // High: 1 average
            assert_eq!(result[0].peaks().values()[0].value, 500);
            assert_eq!(result[1].peaks().values().len(), 1); // Base: 1 average
            assert_eq!(result[1].peaks().values()[0].value, 400);
            assert_eq!(result[2].peaks().values().len(), 2); // Low: 2 averages
            assert_eq!(result[2].peaks().values()[0].value, 300);
            assert_eq!(result[2].peaks().values()[1].value, 200);
        }

        #[test]
        fn map_periods_all_matching_catch_all_period() {
            // Test All matching where one period catches everything
            static PERIODS_ARRAY: [CostPeriod; 2] = [
                CostPeriod::builder()
                    .load(LoadType::High)
                    .fixed_cost(10, 0)
                    .hours(6, 12)
                    .build(),
                CostPeriod::builder()
                    .load(LoadType::Low)
                    .fixed_cost(5, 0)
                    .build(), // Catch-all period
            ];
            let periods = CostPeriods::new_all(&PERIODS_ARRAY);

            let averages = vec![
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 10, 0, 0), 500), // High + Low
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 20, 0, 0), 300), // Low only
            ];

            let result = PeriodPeakMatches::new(
                TariffCalculationMethod::AverageHours(10),
                &periods,
                &averages
                    .iter()
                    .map(|a| a.into_virtual(None))
                    .collect::<Vec<_>>(),
                CostPeriodMatching::All,
            );

            assert_eq!(result.len(), 2);
            // High period gets one
            assert_eq!(result[0].peaks().values().len(), 1);
            assert_eq!(result[0].peaks().values()[0].value, 500);
            // Low period (catch-all) gets both
            assert_eq!(result[1].peaks().values().len(), 2);
            assert_eq!(result[1].peaks().values()[0].value, 500);
            assert_eq!(result[1].peaks().values()[1].value, 300);
        }

        #[test]
        fn map_periods_order_preservation() {
            // Verify that the order of periods is preserved
            static PERIODS_ARRAY: [CostPeriod; 2] = [
                CostPeriod::builder()
                    .load(LoadType::High)
                    .fixed_cost(10, 0)
                    .hours(6, 12)
                    .build(),
                CostPeriod::builder()
                    .load(LoadType::Low)
                    .fixed_cost(5, 0)
                    .hours(12, 18)
                    .build(),
            ];
            let periods = CostPeriods::new_first(&PERIODS_ARRAY);

            let averages = vec![
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 10, 0, 0), 500),
                PowerAverage::new(Stockholm.dt(2025, 1, 15, 14, 0, 0), 400),
            ];

            let result = PeriodPeakMatches::new(
                TariffCalculationMethod::AverageHours(10),
                &periods,
                &averages
                    .iter()
                    .map(|a| a.into_virtual(None))
                    .collect::<Vec<_>>(),
                CostPeriodMatching::First,
            );

            assert_eq!(result.len(), 2);
            // Verify periods are in the same order by checking their peak values
            assert_eq!(result[0].peaks().values()[0].value, 500); // High period
            assert_eq!(result[1].peaks().values()[0].value, 400); // Low period
        }
    }
}
