"""End-to-end integration test: login → assessment → scoring → PDF export.

Uses an in-memory SQLite database. All service calls go through the real
service layer and scoring engine; only get_engine() is patched to prevent
any writes to fmcg.db.
"""
from __future__ import annotations

from datetime import date
from unittest.mock import patch

import pytest
from sqlmodel import Session, SQLModel, create_engine

from models import (
    AssessmentResults,
    CertificationLevels,
    Companies,
    Requirements,
    TechnologyClasses,
)
from models.auth import Users
from services.auth import hash_password


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

_ENGINE_TARGETS = [
    "db.session.get_engine",
    "services.assessment.get_engine",
    "services.auth.get_engine",
    "services.results.get_engine",
    "services.history.get_engine",
]


@pytest.fixture(scope="module")
def mem_engine():
    engine = create_engine("sqlite:///:memory:")
    SQLModel.metadata.create_all(engine)
    return engine


@pytest.fixture(scope="module", autouse=True)
def patch_engine(mem_engine):
    patches = [patch(t, return_value=mem_engine) for t in _ENGINE_TARGETS]
    for p in patches:
        p.start()
    yield mem_engine
    for p in patches:
        p.stop()


@pytest.fixture(scope="module")
def seeded_db(mem_engine):
    """Seed minimal data: classes, requirements, cert levels, company, user."""
    with Session(mem_engine) as s:
        # Technology classes
        classes = {
            code: TechnologyClasses(code=code, name=f"Клас {code}")
            for code in ("I", "II", "III", "IV")
        }
        for tc in classes.values():
            s.add(tc)
        s.commit()
        for tc in classes.values():
            s.refresh(tc)

        # 2 requirements per class (all numeric >=, threshold 50)
        reqs = []
        for code, tc in classes.items():
            for i in (1, 2):
                r = Requirements(
                    code=f"{code}.{i}",
                    class_id=tc.id,
                    threshold="50",
                    operator=">=",
                    mandatory=True,
                    criticality=1,
                    deadline_days=30,
                    recommendation=f"Рекомендація {code}.{i}",
                )
                s.add(r)
                reqs.append(r)
        s.commit()

        # Certification levels (§С.4.6 two-dimensional)
        s.add(CertificationLevels(level=1, name="Рівень 1", class_min_index=0.75,
                                  overall_min_index=0.70, class_scope="I,II"))
        s.add(CertificationLevels(level=2, name="Рівень 2", class_min_index=0.80,
                                  overall_min_index=0.82, class_scope="ALL"))
        s.add(CertificationLevels(level=3, name="Рівень 3", class_min_index=0.95,
                                  overall_min_index=0.95, class_scope="ALL"))
        s.commit()

        # Company
        company = Companies(name="ТОВ Тест", edrpou="12345678")
        s.add(company)
        s.commit()
        s.refresh(company)

        # Expert user
        user = Users(
            email="expert@test.ua",
            hashed_password=hash_password("pass123"),
            role="Expert",
        )
        s.add(user)
        s.commit()

    return mem_engine


# ---------------------------------------------------------------------------
# 12.1 — Full end-to-end flow
# ---------------------------------------------------------------------------

def test_authenticate_valid_credentials(seeded_db):
    from services.auth import authenticate
    user = authenticate("expert@test.ua", "pass123")
    assert user is not None
    assert user.role == "Expert"


def test_authenticate_wrong_password(seeded_db):
    from services.auth import authenticate
    assert authenticate("expert@test.ua", "wrong") is None


def test_create_assessment(seeded_db):
    from services.assessment import create_assessment
    aid = create_assessment(
        company_id=1,
        assessment_date=date(2026, 6, 29),
        expert="expert@test.ua",
    )
    assert aid == 1


def test_save_all_answers_passing(seeded_db, mem_engine):
    """Save answers that satisfy every requirement (value=80 >= threshold=50)."""
    from services.assessment import save_answers
    with Session(mem_engine) as s:
        req_ids = [r.id for r in s.exec(
            __import__("sqlmodel").select(Requirements)
        ).all()]

    answers = {rid: "80" for rid in req_ids}
    saved = save_answers(assessment_id=1, answers=answers)
    assert saved == len(req_ids)


def test_run_assessment_returns_level(seeded_db):
    """After scoring with all answers passing, certification level must be set."""
    from services.assessment import run_assessment
    result = run_assessment(assessment_id=1)
    assert result.certification_level is not None
    assert result.certification_level >= 1


def test_assessment_summary_has_level(seeded_db):
    from services.results import get_assessment_summary
    summary = get_assessment_summary(assessment_id=1)
    assert summary is not None
    assert summary["status"] == "scored"
    assert summary["certification_level"] is not None


def test_class_results_all_met(seeded_db):
    """All requirements passed → every class index should be 1.0."""
    from services.results import get_class_results
    rows = get_class_results(assessment_id=1)
    assert len(rows) == 4  # I, II, III, IV
    for row in rows:
        assert row["index"] == pytest.approx(1.0), f"Class {row['class']} index not 1.0"


def test_no_nonconformities_when_all_pass(seeded_db):
    from services.results import get_nonconformities
    ncs = get_nonconformities(assessment_id=1)
    assert ncs == []


def test_pdf_export_produces_bytes(seeded_db):
    """PDF export with Cyrillic text must return non-empty bytes."""
    from export.pdf import generate_pdf
    pdf_bytes = generate_pdf(
        company="ТОВ Тест",
        assessment_date=date(2026, 6, 29),
        expert="expert@test.ua",
        certification_level=3,
        class_results=[
            {"class": "I", "name": "Клас I", "total": 2, "met": 2, "index": 1.0},
            {"class": "II", "name": "Клас II", "total": 2, "met": 2, "index": 1.0},
        ],
        nonconformities=[],
    )
    assert isinstance(pdf_bytes, bytes)
    assert len(pdf_bytes) > 1_000  # non-trivial PDF


def test_assessment_with_one_failing_req(seeded_db, mem_engine):
    """Second assessment: one requirement fails → nonconformity recorded."""
    from services.assessment import create_assessment, save_answers, run_assessment
    from services.results import get_nonconformities

    aid = create_assessment(
        company_id=1,
        assessment_date=date(2026, 6, 29),
        expert="expert@test.ua",
    )

    with Session(mem_engine) as s:
        reqs = s.exec(__import__("sqlmodel").select(Requirements)).all()

    # All pass except the first requirement (value=10 < threshold=50)
    answers = {r.id: "10" if reqs.index(r) == 0 else "80" for r in reqs}
    save_answers(assessment_id=aid, answers=answers)

    result = run_assessment(assessment_id=aid)
    assert len(result.nonconformity_req_ids) == 1

    ncs = get_nonconformities(assessment_id=aid)
    assert len(ncs) == 1
    assert ncs[0]["is_critical"] is False  # criticality=1 < 3
