"""
tests/test_acceptance.py — Acceptance tests for the Technical File pipeline.

These tests verify the full pipeline from findings → Technical File → output,
exercising all integration points as a compliance officer would experience them.

Scenarios:
  1. Clean model: all tests pass → COMPLIANT status
  2. Vulnerable model: critical findings → NON_COMPLIANT status
  3. Mitigated model: failures but all MITIGATED → sections are MITIGATED
  4. Full pipeline with AIBoM + attestation + differential report
  5. Database save/retrieve round-trip
  6. Technical File JSON is self-describing (all required fields)
  7. HTML output is human-readable
"""
from __future__ import annotations

import json
import tempfile
from pathlib import Path
from unittest.mock import patch

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from sichgate.database import Base, DatabaseManager
from sichgate.technical_file_builder import TechnicalFile, TechnicalFileBuilder
from sichgate.pdf_exporter import PDFExporter


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _finding(attack_type: str, status: str, severity: str = "high",
             finding_status: str = "NEW") -> dict:
    return {
        "attack_name": f"acc_{attack_type}",
        "attack_type": attack_type,
        "status": status,
        "severity": severity if status in ("fail", "warning") else None,
        "notes": f"Acceptance test: {attack_type}",
        "remediation": "Apply controls." if status in ("fail", "warning") else None,
        "finding_status": finding_status,
        "compliance_tags": ["EU-AI-ACT-ART-9"],
        "success_rate": 0.8 if status == "fail" else 0.1,
    }


def _aibom(model_name: str = "acceptance-model") -> dict:
    return {
        "bomFormat": "CycloneDX",
        "specVersion": "1.6",
        "serialNumber": "urn:uuid:acc-001",
        "version": 1,
        "metadata": {
            "timestamp": "2026-03-02T12:00:00+00:00",
            "component": {
                "type": "machine-learning-model",
                "name": model_name,
                "version": "3.0",
                "hashes": [{"alg": "SHA-256", "content": "acc123" * 10}],
            },
        },
        "properties": [
            {"name": "sichgate:hardware:cpu_model", "value": "Intel Xeon"},
        ],
    }


@pytest.fixture
def db_manager():
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    manager = DatabaseManager.__new__(DatabaseManager)
    manager._SessionFactory = Session
    return manager


# ---------------------------------------------------------------------------
# Scenario 1: Clean model — all pass → COMPLIANT
# ---------------------------------------------------------------------------

def test_clean_model_is_compliant():
    findings = [
        _finding("prompt_injection", "pass"),
        _finding("bias_fairness", "pass"),
        _finding("hallucination", "pass"),
        _finding("alignment", "pass"),
    ]
    builder = TechnicalFileBuilder(findings=findings, aibom=_aibom())
    tf = builder.build()

    assert tf.executive_summary["overall_compliance_status"] == "COMPLIANT"
    assert tf.executive_summary["fail_count"] == 0
    assert tf.executive_summary["critical_count"] == 0
    # Key testing sections should be COMPLIANT
    assert tf.sections["2g"].compliance_status == "COMPLIANT"


# ---------------------------------------------------------------------------
# Scenario 2: Vulnerable model — critical findings → NON_COMPLIANT
# ---------------------------------------------------------------------------

def test_vulnerable_model_is_non_compliant():
    findings = [
        _finding("prompt_injection", "fail", "critical"),
        _finding("jailbreak", "fail", "critical"),
        _finding("bias_fairness", "fail", "high"),
    ]
    builder = TechnicalFileBuilder(findings=findings, aibom=_aibom())
    tf = builder.build()

    assert tf.executive_summary["overall_compliance_status"] == "NON_COMPLIANT"
    assert tf.executive_summary["critical_count"] == 2
    assert tf.sections["2g"].compliance_status == "NON_COMPLIANT"
    assert tf.sections["5"].compliance_status == "NON_COMPLIANT"


# ---------------------------------------------------------------------------
# Scenario 3: Mitigated model — all failures resolved → MITIGATED
# ---------------------------------------------------------------------------

def test_mitigated_model_sections_are_mitigated():
    findings = [
        _finding("prompt_injection", "fail", "high", finding_status="MITIGATED"),
        _finding("bias_fairness", "fail", "medium", finding_status="MITIGATED"),
    ]
    builder = TechnicalFileBuilder(findings=findings, aibom=_aibom())
    tf = builder.build()

    assert tf.sections["2g"].compliance_status == "MITIGATED"
    assert tf.sections["5"].compliance_status == "MITIGATED"


# ---------------------------------------------------------------------------
# Scenario 4: Full pipeline with all artefacts
# ---------------------------------------------------------------------------

def test_full_pipeline_with_all_artefacts():
    findings = [
        _finding("prompt_injection", "fail", "critical"),
        _finding("bias_fairness", "warning", "medium"),
        _finding("hallucination", "pass"),
    ]
    diff = {
        "run_id": "acc-diff-001",
        "base_model_id": "base",
        "custom_model_id": "custom",
        "overall_risk_delta": 0.25,
        "critical_alerts": ["CRITICAL Article 15"],
        "differential_analysis": {},
    }
    att = {
        "attestation_id": "acc-att-001",
        "algorithm": "ed25519",
        "signature": "sig==",
        "public_key_b64": "pub==",
        "payload_hash": "hash",
        "timestamp": "2026-03-02T12:00:00+00:00",
    }

    builder = TechnicalFileBuilder(
        findings=findings,
        aibom=_aibom(),
        differential_report=diff,
        attestation=att,
    )
    tf = builder.build()

    # All artefacts preserved
    assert tf.attestation["attestation_id"] == "acc-att-001"
    assert tf.differential_report["run_id"] == "acc-diff-001"
    assert tf.model_metadata["name"] == "acceptance-model"

    # Executive summary flags
    assert tf.executive_summary["has_attestation"] is True
    assert tf.executive_summary["has_aibom"] is True
    assert tf.executive_summary["has_differential"] is True

    # Total findings
    assert tf.executive_summary["total_findings"] == 3

    # HTML renders without error
    exporter = PDFExporter()
    html = exporter.render_html(tf)
    assert "acceptance-model" in html
    assert "acc-att-001" in html
    assert "{{ " not in html  # No unresolved Jinja2 tags


# ---------------------------------------------------------------------------
# Scenario 5: Database round-trip
# ---------------------------------------------------------------------------

def test_database_save_and_retrieve(db_manager):
    findings = [_finding("prompt_injection", "fail", "high")]
    builder = TechnicalFileBuilder(findings=findings, aibom=_aibom("db-test-model"))
    tf = builder.build()

    record_id = db_manager.save_technical_file(tf)
    assert record_id == tf.test_run_id

    retrieved = db_manager.get_technical_file(tf.test_run_id)
    assert retrieved is not None
    assert retrieved["model_metadata"]["name"] == "db-test-model"
    assert "sections" in retrieved

    listed = db_manager.list_technical_files()
    assert len(listed) == 1
    assert listed[0]["model_name"] == "db-test-model"


# ---------------------------------------------------------------------------
# Scenario 6: JSON output is self-describing
# ---------------------------------------------------------------------------

def test_json_output_is_self_describing():
    findings = [_finding("prompt_injection", "fail", "critical")]
    builder = TechnicalFileBuilder(findings=findings, aibom=_aibom())
    tf = builder.build()

    raw_json = tf.to_json()
    data = json.loads(raw_json)

    # All top-level required fields
    required_fields = {
        "test_run_id", "timestamp", "model_metadata",
        "sections", "executive_summary",
    }
    assert required_fields.issubset(data.keys())

    # Each section has required keys
    for sid, section in data["sections"].items():
        assert "section_id" in section
        assert "title" in section
        assert "requirement" in section
        assert "compliance_status" in section
        assert "narrative" in section
        assert "findings" in section


# ---------------------------------------------------------------------------
# Scenario 7: HTML is human-readable (spot checks)
# ---------------------------------------------------------------------------

def test_html_output_is_human_readable():
    findings = [
        _finding("prompt_injection", "fail", "critical"),
        _finding("bias_fairness", "pass"),
    ]
    builder = TechnicalFileBuilder(findings=findings, aibom=_aibom("readable-model"))
    tf = builder.build()

    exporter = PDFExporter()
    html = exporter.render_html(tf)

    # Key compliance terms should be present
    assert "EU AI Act" in html
    assert "Annex IV" in html
    assert "readable-model" in html
    assert "Executive Summary" in html
    # Section headings
    assert "§2g" in html or "2g" in html
    assert "§5" in html or "5" in html
    # Findings table headers
    assert "Attack" in html
    assert "Severity" in html
