"""MVP sign-off acceptance criteria from requirement.txt §25 and ROADMAP."""

import json
import time
from datetime import date, timedelta
from decimal import Decimal
from pathlib import Path

import pytest

from app.core.security import get_password_hash
from app.models.agency import Agency
from app.models.customer import Customer
from app.models.enums import PaymentStatus, PolicyStatus, UserRole
from app.models.policy import Policy
from app.models.user import User
from app.parsers.registry import PARSER_REGISTRY
from app.repositories.policy_repository import PolicyRepository
from app.services.dashboard_service import DashboardService
from app.services.search_service import SearchService
from tests.fixtures.pending_insurer_samples import PENDING_INSURER_SAMPLES
from tests.test_insurer_parsers import FIXTURES_PATH, KEY_FIELDS, MIN_KEY_FIELDS

EVALUATION_REPORT = Path(__file__).resolve().parents[1] / "example-pdf" / "evaluation_report.json"
ACCURACY_TARGET = 0.80
SEARCH_MS_BUDGET = 500
SEARCH_POLICY_COUNT = 10_000

PROTECTED_GET_ENDPOINTS = [
    "/api/v1/dashboard/summary",
    "/api/v1/policies",
    "/api/v1/customers",
    "/api/v1/search?q=test",
    "/api/v1/reminders",
    "/api/v1/insurance-companies",
    "/api/v1/reports/types",
    "/api/v1/notifications/unread-count",
    "/api/v1/auth/me",
]

PUBLIC_GET_ENDPOINTS = [
    "/api/v1/health",
]


@pytest.mark.acceptance
def test_pdf_extraction_overall_accuracy_on_sample_pdfs():
    """Available real PDF corpus averages ≥80% of 5 mandatory key fields."""
    if not EVALUATION_REPORT.exists():
        pytest.skip("Run scripts/evaluate_pdfs.py to generate example-pdf/evaluation_report.json")

    rows = json.loads(EVALUATION_REPORT.read_text(encoding="utf-8"))
    assert len(rows) >= 10, "Need at least 10 sample PDF evaluations"
    accuracies = [row["key_fields_found"] / len(KEY_FIELDS) for row in rows]
    overall = sum(accuracies) / len(accuracies)
    assert overall >= ACCURACY_TARGET, f"Overall accuracy {overall:.0%} below {ACCURACY_TARGET:.0%}"


@pytest.mark.acceptance
def test_pdf_extraction_insurers_with_ten_samples_meet_eighty_percent():
    """Insurers with ≥10 sample PDFs must average ≥80% key-field accuracy."""
    if not EVALUATION_REPORT.exists():
        pytest.skip("evaluation_report.json not found")

    rows = json.loads(EVALUATION_REPORT.read_text(encoding="utf-8"))
    by_insurer: dict[str, list[float]] = {}
    for row in rows:
        code = row["company_code"]
        by_insurer.setdefault(code, []).append(row["key_fields_found"] / len(KEY_FIELDS))

    checked = {code: scores for code, scores in by_insurer.items() if len(scores) >= 10}
    if not checked:
        pytest.skip("No insurer has ≥10 sample PDFs yet — verified via overall corpus test")

    failures = []
    for code, scores in sorted(checked.items()):
        avg = sum(scores) / len(scores)
        if avg < ACCURACY_TARGET:
            failures.append(f"{code}: {avg:.0%}")

    assert not failures, "Insurers with ≥10 samples below 80%: " + "; ".join(failures)


@pytest.mark.acceptance
def test_pdf_extraction_synthetic_fixtures_meet_minimum_thresholds():
    """All registered insurer parsers meet configured minimum field counts on fixtures."""
    samples = json.loads(FIXTURES_PATH.read_text(encoding="utf-8"))
    for company_code, min_fields in MIN_KEY_FIELDS.items():
        if company_code in PENDING_INSURER_SAMPLES:
            text = PENDING_INSURER_SAMPLES[company_code]
        else:
            text = samples[company_code]["text"]
        result = PARSER_REGISTRY[company_code](text)
        field_map = result.get_field_map()
        found = sum(1 for key in KEY_FIELDS if field_map.get(key) and field_map[key].value)
        assert found >= min_fields, f"{company_code}: {found}/{len(KEY_FIELDS)} key fields"


@pytest.mark.acceptance
def test_search_returns_under_500ms_with_10000_policies(db):
    agency = Agency(name="Perf Agency", phone="9000000001", email="perf@test.local")
    db.add(agency)
    db.flush()

    customer = Customer(agency_id=agency.id, name="Perf Customer", mobile="9000000001")
    db.add(customer)
    db.flush()

    today = date.today()
    policies = [
        Policy(
            agency_id=agency.id,
            customer_id=customer.id,
            policy_number=f"PERF-{i:05d}",
            policy_end_date=today + timedelta(days=i % 365),
            total_commission=Decimal("1000"),
            total_paid=Decimal("0"),
            pending_amount=Decimal("1000"),
            payment_status=PaymentStatus.PENDING,
            status=PolicyStatus.ACTIVE,
        )
        for i in range(SEARCH_POLICY_COUNT)
    ]
    db.bulk_save_objects(policies)
    db.commit()

    service = SearchService(db)
    start = time.perf_counter()
    items, total = service.search(agency.id, "PERF-001", page=1, page_size=20)
    elapsed_ms = (time.perf_counter() - start) * 1000

    assert total >= 1
    assert elapsed_ms < SEARCH_MS_BUDGET, f"Search took {elapsed_ms:.0f}ms (budget {SEARCH_MS_BUDGET}ms)"


@pytest.mark.acceptance
def test_dashboard_expiry_counts_match_repository(db, seed_data):
    agency_id = seed_data["agency"].id
    repo = PolicyRepository(db)
    dashboard = DashboardService(db).get_summary(agency_id)

    today = date.today()
    week_end = today + timedelta(days=6)
    next_week_start = today + timedelta(days=7)
    next_week_end = today + timedelta(days=13)
    month_end = today + timedelta(days=30)

    assert dashboard.expiring_today == repo.count_expiring_between(agency_id, today, today)
    assert dashboard.expiring_this_week == repo.count_expiring_between(agency_id, today, week_end)
    assert dashboard.expiring_next_week == repo.count_expiring_between(agency_id, next_week_start, next_week_end)
    assert dashboard.expiring_this_month == repo.count_expiring_between(agency_id, today, month_end)
    assert dashboard.total_active_policies == repo.count_active(agency_id)


@pytest.mark.acceptance
def test_payment_totals_match_sum_across_policies(db, seed_data):
    agency_id = seed_data["agency"].id
    repo = PolicyRepository(db)
    dashboard = DashboardService(db).get_summary(agency_id)

    assert float(dashboard.total_pending_payments) == pytest.approx(repo.sum_pending_payments(agency_id))
    assert dashboard.policies_with_pending_payments == repo.count_pending_payment_policies(agency_id)


@pytest.mark.acceptance
def test_agency_a_cannot_access_agency_b_data(client, db):
    agency_a = Agency(name="Agency A", phone="9111111111", email="a@test.local")
    agency_b = Agency(name="Agency B", phone="9222222222", email="b@test.local")
    db.add_all([agency_a, agency_b])
    db.flush()

    user_a = User(
        agency_id=agency_a.id,
        email="agent-a@test.com",
        full_name="Agent A",
        hashed_password=get_password_hash("Test@12345"),
        role=UserRole.ADMIN,
        is_active=True,
    )
    db.add(user_a)

    customer_b = Customer(agency_id=agency_b.id, name="B Customer", mobile="9888888888")
    db.add(customer_b)
    db.flush()

    policy_b = Policy(
        agency_id=agency_b.id,
        customer_id=customer_b.id,
        policy_number="POL-B-001",
        policy_end_date=date.today() + timedelta(days=30),
        total_commission=Decimal("500"),
        total_paid=Decimal("0"),
        pending_amount=Decimal("500"),
        payment_status=PaymentStatus.PENDING,
        status=PolicyStatus.ACTIVE,
    )
    db.add(policy_b)
    db.commit()

    login = client.post("/api/v1/auth/login", json={"email": "agent-a@test.com", "password": "Test@12345"})
    assert login.status_code == 200
    headers = {"Authorization": f"Bearer {login.json()['access_token']}"}

    assert client.get(f"/api/v1/policies/{policy_b.id}", headers=headers).status_code == 404
    assert client.get(f"/api/v1/customers/{customer_b.id}", headers=headers).status_code == 404
    assert client.get("/api/v1/policies", headers=headers).json()["total"] == 0


@pytest.mark.acceptance
@pytest.mark.parametrize("path", PROTECTED_GET_ENDPOINTS)
def test_protected_endpoints_reject_unauthenticated(client, path):
    assert client.get(path).status_code == 401


@pytest.mark.acceptance
@pytest.mark.parametrize("path", PUBLIC_GET_ENDPOINTS)
def test_public_endpoints_allow_unauthenticated(client, path):
    response = client.get(path)
    assert response.status_code == 200
