use crate::{
    N00,
    factor::{Factor, VarId, VarStore},
    gaussian::Gaussian,
};

/// Computes the weighted sum of player performances into a team-perf var.
///
/// Inputs are pre-computed player performance Gaussians (i.e., rating priors
/// already with beta² noise added via `Rating::performance()`). The factor
/// runs once per game and writes the weighted sum to the output var.
#[derive(Debug)]
pub struct TeamSumFactor {
    pub inputs: Vec<(Gaussian, f64)>,
    pub out: VarId,
}

impl Factor for TeamSumFactor {
    fn propagate(&mut self, vars: &mut VarStore) -> (f64, f64) {
        let perf = self.inputs.iter().fold(N00, |acc, (g, w)| acc + (*g * *w));
        let old = vars.get(self.out);
        vars.set(self.out, perf);
        old.delta(perf)
    }
}

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

    #[test]
    fn single_player_unit_weight() {
        let mut vars = VarStore::new();
        let out = vars.alloc(N_INF);
        let g = Gaussian::from_ms(25.0, 5.0);
        let mut f = TeamSumFactor {
            inputs: vec![(g, 1.0)],
            out,
        };

        f.propagate(&mut vars);
        let result = vars.get(out);
        assert!((result.mu() - 25.0).abs() < 1e-12);
        assert!((result.sigma() - 5.0).abs() < 1e-12);
    }

    #[test]
    fn two_players_summed() {
        let mut vars = VarStore::new();
        let out = vars.alloc(N_INF);
        let g1 = Gaussian::from_ms(20.0, 3.0);
        let g2 = Gaussian::from_ms(30.0, 4.0);
        let mut f = TeamSumFactor {
            inputs: vec![(g1, 1.0), (g2, 1.0)],
            out,
        };

        f.propagate(&mut vars);
        let result = vars.get(out);
        // sum: mu = 20 + 30 = 50, var = 9 + 16 = 25, sigma = 5
        assert!((result.mu() - 50.0).abs() < 1e-12);
        assert!((result.sigma() - 5.0).abs() < 1e-12);
    }

    #[test]
    fn weighted_inputs() {
        let mut vars = VarStore::new();
        let out = vars.alloc(N_INF);
        let g = Gaussian::from_ms(10.0, 2.0);
        let mut f = TeamSumFactor {
            inputs: vec![(g, 2.0)],
            out,
        };

        f.propagate(&mut vars);
        let result = vars.get(out);
        // g * 2.0: mu = 10*2 = 20, sigma = 2*2 = 4
        assert!((result.mu() - 20.0).abs() < 1e-12);
        assert!((result.sigma() - 4.0).abs() < 1e-12);
    }

    #[test]
    fn delta_is_zero_on_repeat_propagate() {
        let mut vars = VarStore::new();
        let out = vars.alloc(N_INF);
        let g = Gaussian::from_ms(5.0, 1.0);
        let mut f = TeamSumFactor {
            inputs: vec![(g, 1.0)],
            out,
        };

        f.propagate(&mut vars);
        let (dmu, dsig) = f.propagate(&mut vars);
        assert!(dmu < 1e-12, "expected ~0 delta on repeat, got {}", dmu);
        assert!(dsig < 1e-12);
    }
}
