"""Tests for financial KPIs, Monte Carlo, and cohort analysis."""

import numpy as np
import pandas as pd
import pytest

import bizanalyticsai.finance as finance


@pytest.fixture
def transactions():
    np.random.seed(42)
    n = 500
    return pd.DataFrame({
        "customer_id": np.random.choice(range(100), n),
        "date":        pd.date_range("2022-01-01", periods=n, freq="3D"),
        "revenue":     np.random.lognormal(5, 1, n),
        "cost":        np.random.lognormal(4, 1, n),
    })


class TestMetrics:
    def test_calc_ltv(self):
        ltv = finance.calc_ltv(arpu=200, monthly_churn_pct=5)
        assert ltv == pytest.approx(4000.0, rel=0.01)

    def test_calc_ltv_zero_churn(self):
        assert finance.calc_ltv(200, 0) == float("inf")

    def test_calc_ltv_cac(self):
        ratio = finance.calc_ltv_cac(ltv=3000, cac=500)
        assert ratio == pytest.approx(6.0, rel=0.01)

    def test_calc_payback(self):
        pb = finance.calc_payback(cac=1000, arpu=200, gross_margin_pct=80)
        assert pb == pytest.approx(6.25, rel=0.01)

    def test_calc_arr(self):
        assert finance.calc_arr(10000) == 120000.0

    def test_calc_gross_margin(self):
        gm = finance.calc_gross_margin(revenue=100, cogs=30)
        assert gm == pytest.approx(70.0, rel=0.01)

    def test_break_even_viable(self):
        result = finance.break_even(fixed_costs=10000, unit_price=50, unit_cost=20)
        assert result["viable"] is True
        assert result["units"] == 334
        assert result["margin_pct"] == pytest.approx(60.0, rel=0.01)

    def test_break_even_not_viable(self):
        result = finance.break_even(fixed_costs=1000, unit_price=10, unit_cost=15)
        assert result["viable"] is False

    def test_roi(self):
        assert finance.roi(investment=10000, net_profit=2500) == pytest.approx(25.0)

    def test_calc_mrr(self, transactions):
        mrr = finance.calc_mrr(transactions, "revenue", "date")
        assert isinstance(mrr, pd.DataFrame)
        assert "mrr" in mrr.columns
        assert "growth_pct" in mrr.columns
        assert len(mrr) >= 1

    def test_kpis(self, transactions):
        k = finance.kpis(
            transactions,
            revenue_col="revenue",
            cost_col="cost",
            customer_col="customer_id",
            date_col="date",
            cac=300,
            monthly_churn_pct=5,
        )
        assert isinstance(k, finance.FinancialKPIs)
        assert k.mrr > 0
        assert k.arr == pytest.approx(k.mrr * 12, rel=0.01)
        assert k.gross_margin <= 100
        assert k.ltv > 0


class TestMonteCarlo:
    def test_basic_simulation(self):
        result = finance.montecarlo(
            initial_revenue=50_000,
            growth_pct=8,
            fixed_costs=20_000,
            variable_cost_pct=20,
            initial_investment=200_000,
            months=12,
            iterations=1_000,
            seed=42,
        )
        assert result.iterations == 1000
        assert result.months == 12
        assert result.p10 <= result.p50 <= result.p90
        assert 0 <= result.bankruptcy_probability <= 100
        assert 0 <= result.positive_probability <= 100

    def test_sorted_results(self):
        result = finance.montecarlo(
            initial_revenue=10_000, growth_pct=5, fixed_costs=8_000,
            months=6, iterations=500, seed=0,
        )
        results = result.results
        assert results == sorted(results)

    def test_to_dataframe(self):
        result = finance.montecarlo(
            initial_revenue=10_000, growth_pct=5, fixed_costs=8_000,
            months=6, iterations=500, seed=0,
        )
        df = result.to_dataframe()
        assert len(df) == 5
        assert "percentile" in df.columns
        assert "value" in df.columns

    def test_simulate_paths(self):
        paths = finance.simulate_paths(
            initial_revenue=20_000, growth_pct=6, fixed_costs=10_000,
            months=12, n_paths=10, seed=1,
        )
        assert isinstance(paths, pd.DataFrame)
        assert "path_id" in paths.columns
        assert "month" in paths.columns
        assert paths["path_id"].nunique() == 10

    def test_sensitivity_analysis(self):
        base = dict(initial_revenue=30_000, growth_pct=8, fixed_costs=15_000,
                    variable_cost_pct=20, initial_investment=100_000, months=12)
        result = finance.sensitivity_analysis(
            base_params=base,
            param_ranges={"growth_pct": (0, 20, 5)},
        )
        assert isinstance(result, pd.DataFrame)
        assert len(result) == 5
        assert "parameter" in result.columns

    def test_histogram_bins(self):
        result = finance.montecarlo(
            initial_revenue=10_000, growth_pct=5, fixed_costs=8_000,
            months=6, iterations=500, seed=0,
        )
        bins = finance.histogram_bins(result.results, n_bins=20)
        assert len(bins) == 20
        assert "count" in bins.columns
        assert "is_negative" in bins.columns


class TestCohort:
    @pytest.fixture
    def cohort_df(self):
        np.random.seed(42)
        rows = []
        for cid in range(50):
            start = np.random.randint(0, 6)
            duration = np.random.randint(1, 13)
            for m in range(start, start + duration):
                rows.append({
                    "customer_id": cid,
                    "date": pd.Timestamp("2023-01-01") + pd.DateOffset(months=m),
                    "revenue": np.random.uniform(100, 500),
                })
        return pd.DataFrame(rows)

    def test_cohort_retention_shape(self, cohort_df):
        retention = finance.cohort_retention(cohort_df, "customer_id", "date", "M")
        assert isinstance(retention, pd.DataFrame)
        assert len(retention.columns) >= 1

    def test_cohort_revenue(self, cohort_df):
        rev = finance.cohort_revenue(cohort_df, "customer_id", "date", "revenue", "M")
        assert isinstance(rev, pd.DataFrame)
        assert "Period 0 (base)" in rev.columns

    def test_cohort_summary(self, cohort_df):
        summary = finance.cohort_summary(cohort_df, "customer_id", "date", "revenue")
        assert isinstance(summary, dict)
        assert "n_cohorts" in summary
