from __future__ import annotations

from fastapi.testclient import TestClient
from unittest.mock import Mock

from models import Project, Scenario
from services.reporting import (
    ReportingService,
    ReportFilters,
    IncludeOptions,
    ScenarioReport,
    ScenarioFinancialTotals,
    ScenarioDeterministicMetrics,
)
from routes.reports import router as reports_router


class TestReportingService:
    def test_build_project_summary_context(self, unit_of_work_factory):
        with unit_of_work_factory() as uow:
            project = Project(name="Test Project", location="Test Location")
            uow.projects.create(project)
            scenario = Scenario(project_id=project.id,
                                name="Test Scenario", status="draft")
            uow.scenarios.create(scenario)
            uow.commit()

            service = ReportingService(uow)
            request = Mock()
            request.url_for = Mock(return_value="/api/reports/projects/1")

            filters = ReportFilters()
            include = IncludeOptions()

            context = service.build_project_summary_context(
                project, filters, include, 500, (5.0, 50.0, 95.0), request
            )

            assert "project" in context
            assert context["scenario_count"] == 1
            assert "aggregates" in context
            assert "scenarios" in context
            assert context["title"] == f"Project Summary · {project.name}"

    def test_build_scenario_comparison_context(self, unit_of_work_factory):
        with unit_of_work_factory() as uow:
            project = Project(name="Test Project", location="Test Location")
            uow.projects.create(project)
            scenario1 = Scenario(project_id=project.id,
                                 name="Scenario 1", status="draft")
            scenario2 = Scenario(project_id=project.id,
                                 name="Scenario 2", status="active")
            uow.scenarios.create(scenario1)
            uow.scenarios.create(scenario2)
            uow.commit()

            service = ReportingService(uow)
            request = Mock()
            request.url_for = Mock(
                return_value="/api/reports/projects/1/comparison")

            include = IncludeOptions()
            scenarios = [scenario1, scenario2]

            context = service.build_scenario_comparison_context(
                project, scenarios, include, 500, (5.0, 50.0, 95.0), request
            )

            assert "project" in context
            assert "scenarios" in context
            assert "comparison" in context
            assert context["title"] == f"Scenario Comparison · {project.name}"

    def test_build_scenario_distribution_context(self, unit_of_work_factory):
        with unit_of_work_factory() as uow:
            project = Project(name="Test Project", location="Test Location")
            uow.projects.create(project)
            scenario = Scenario(project_id=project.id,
                                name="Test Scenario", status="draft")
            uow.scenarios.create(scenario)
            uow.commit()

            service = ReportingService(uow)
            request = Mock()
            request.url_for = Mock(
                return_value="/api/reports/scenarios/1/distribution")

            include = IncludeOptions()

            context = service.build_scenario_distribution_context(
                scenario, include, 500, (5.0, 50.0, 95.0), request
            )

            assert "scenario" in context
            assert "summary" in context
            assert "metrics" in context
            assert "monte_carlo" in context
            assert context["title"] == f"Scenario Distribution · {scenario.name}"

    def test_scenario_report_to_dict_with_enum_status(self, unit_of_work_factory):
        """Test that to_dict handles enum status values correctly."""
        with unit_of_work_factory() as uow:
            project = Project(name="Test Project", location="Test Location")
            uow.projects.create(project)
            scenario = Scenario(
                project_id=project.id,
                name="Test Scenario",
                status="draft",  # Stored as string
                primary_resource="diesel"  # Stored as string
            )
            uow.scenarios.create(scenario)
            uow.commit()

            # Create a mock scenario report
            totals = ScenarioFinancialTotals(
                currency="USD",
                inflows=1000.0,
                outflows=500.0,
                net=500.0,
                by_category={}
            )
            deterministic = ScenarioDeterministicMetrics(
                currency="USD",
                discount_rate=0.1,
                compounds_per_year=1,
                npv=100.0,
                irr=0.15,
                payback_period=2.5,
                notes=[]
            )

            report = ScenarioReport(
                scenario=scenario,
                totals=totals,
                deterministic=deterministic,
                monte_carlo=None
            )

            result = report.to_dict()

            assert result["scenario"]["status"] == "draft"  # type: ignore
            # type: ignore
            assert result["scenario"]["primary_resource"] == "diesel"
            assert result["financials"]["net"] == 500.0  # type: ignore
            assert result["metrics"]["npv"] == 100.0  # type: ignore

    def test_project_summary_with_scenario_ids_filter(self, unit_of_work_factory):
        with unit_of_work_factory() as uow:
            project = Project(name="Test Project", location="Test Location")
            uow.projects.create(project)
            scenario1 = Scenario(project_id=project.id,
                                 name="Scenario 1", status="active")
            scenario2 = Scenario(project_id=project.id,
                                 name="Scenario 2", status="draft")
            uow.scenarios.create(scenario1)
            uow.scenarios.create(scenario2)
            uow.commit()

            service = ReportingService(uow)

            # Test filtering by scenario IDs
            filters = ReportFilters(scenario_ids={scenario1.id})
            result = service.project_summary(
                project, filters=filters, include=IncludeOptions(),
                iterations=100, percentiles=(5.0, 50.0, 95.0)
            )

            assert result["scenario_count"] == 1  # type: ignore
            # type: ignore
            # type: ignore
            assert result["scenarios"][0]["scenario"]["name"] == "Scenario 1"


class TestReportingRoutes:
    def test_project_summary_route(self, client: TestClient, unit_of_work_factory):
        with unit_of_work_factory() as uow:
            project = Project(name="Test Project", location="Test Location")
            uow.projects.create(project)
            scenario = Scenario(project_id=project.id,
                                name="Test Scenario", status="draft")
            uow.scenarios.create(scenario)
            uow.commit()

        response = client.get(f"/reports/projects/{project.id}")
        assert response.status_code == 200
        data = response.json()
        assert "project" in data
        assert data["scenario_count"] == 1

    def test_project_summary_html_route(self, client: TestClient, unit_of_work_factory):
        with unit_of_work_factory() as uow:
            project = Project(name="Test Project", location="Test Location")
            uow.projects.create(project)
            scenario = Scenario(project_id=project.id,
                                name="Test Scenario", status="draft")
            uow.scenarios.create(scenario)
            uow.commit()

        response = client.get(f"/reports/projects/{project.id}/ui")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]
        assert "Test Project" in response.text

    def test_scenario_comparison_route(self, client: TestClient, unit_of_work_factory):
        with unit_of_work_factory() as uow:
            project = Project(name="Test Project", location="Test Location")
            uow.projects.create(project)
            scenario1 = Scenario(project_id=project.id,
                                 name="Scenario 1", status="draft")
            scenario2 = Scenario(project_id=project.id,
                                 name="Scenario 2", status="active")
            uow.scenarios.create(scenario1)
            uow.scenarios.create(scenario2)
            uow.commit()

        response = client.get(
            f"/reports/projects/{project.id}/scenarios/compare?scenario_ids={scenario1.id}&scenario_ids={scenario2.id}"
        )
        assert response.status_code == 200
        data = response.json()
        assert "project" in data
        assert "scenarios" in data
        assert "comparison" in data

    def test_scenario_distribution_route(self, client: TestClient, unit_of_work_factory):
        with unit_of_work_factory() as uow:
            project = Project(name="Test Project", location="Test Location")
            uow.projects.create(project)
            scenario = Scenario(project_id=project.id,
                                name="Test Scenario", status="draft")
            uow.scenarios.create(scenario)
            uow.commit()

        response = client.get(
            f"/reports/scenarios/{scenario.id}/distribution")
        assert response.status_code == 200
        data = response.json()
        assert "scenario" in data
        assert "summary" in data
        assert "monte_carlo" in data

    def test_unauthorized_access(self, client: TestClient):
        # Create a new client without authentication
        from fastapi import FastAPI
        app = FastAPI()
        app.include_router(reports_router)
        from fastapi.testclient import TestClient
        unauth_client = TestClient(app)

        response = unauth_client.get("/reports/projects/1")
        assert response.status_code == 401

    def test_project_not_found(self, client: TestClient):
        response = client.get("/reports/projects/999")
        assert response.status_code == 404

    def test_scenario_not_found(self, client: TestClient):
        response = client.get("/reports/scenarios/999/distribution")
        assert response.status_code == 404
