"""Integration tests for Trust5 pipeline correctness.

These tests verify that the pipeline correctly handles:
- Panel width constraints in TUI
- Tool-specific emit codes in agent
- Completeness validator manifest fallback with real profiles
- finalize_status correctly marks failures as TERMINAL
"""

from __future__ import annotations

import os
import tempfile
from dataclasses import dataclass, field
from unittest.mock import MagicMock, patch

import pytest

from trust5.core.config import QualityConfig
from trust5.core.lang_profiles import PROFILES
from trust5.core.message import M
from trust5.core.quality_validators import ProjectCompletenessValidator
from trust5.tui.log_widget import Trust5Log

# ── Panel width constraint tests ──────────────────────────────────────────


def test_panel_max_width_constant_exists():
    """Trust5Log widget must define _PANEL_MAX_WIDTH <= 120 (fits in max-width CSS)."""
    assert hasattr(Trust5Log, "_PANEL_MAX_WIDTH")
    assert Trust5Log._PANEL_MAX_WIDTH <= 120


def test_panel_max_width_is_reasonable():
    """Panel max width must be between 80 and 200 chars."""
    width = Trust5Log._PANEL_MAX_WIDTH
    assert 80 <= width <= 200, f"_PANEL_MAX_WIDTH={width} is outside [80, 200]"


# ── Tool emit code tests ────────────────────────────────────────────────


def test_tool_emit_code_dict_exists():
    """_TOOL_EMIT_CODE must exist and map tool names to M codes."""
    from trust5.core.agent import _TOOL_EMIT_CODE

    assert isinstance(_TOOL_EMIT_CODE, dict)
    assert len(_TOOL_EMIT_CODE) >= 6, "Should have at least 6 tool mappings"


def test_tool_emit_code_maps_core_tools():
    """All core tools must have specific emit codes."""
    from trust5.core.agent import _TOOL_EMIT_CODE

    expected = {
        "Bash": M.TBSH,
        "Read": M.TRED,
        "Write": M.TWRT,
        "Edit": M.TEDT,
        "Glob": M.TGLB,
        "Grep": M.TGRP,
    }
    for tool_name, expected_code in expected.items():
        assert tool_name in _TOOL_EMIT_CODE, f"Missing mapping for {tool_name}"
        assert _TOOL_EMIT_CODE[tool_name] == expected_code, (
            f"{tool_name} should emit {expected_code}, got {_TOOL_EMIT_CODE[tool_name]}"
        )


def test_tool_emit_code_values_are_m_enum():
    """All values in _TOOL_EMIT_CODE must be M enum members."""
    from trust5.core.agent import _TOOL_EMIT_CODE

    for tool_name, code in _TOOL_EMIT_CODE.items():
        assert isinstance(code, M), f"{tool_name} maps to {code!r} which is not an M enum"


def test_tool_emit_unknown_tool_falls_back_to_ctlc():
    """Unknown tool names should fall back to M.CTLC."""
    from trust5.core.agent import _TOOL_EMIT_CODE

    result = _TOOL_EMIT_CODE.get("UnknownTool", M.CTLC)
    assert result == M.CTLC


# ── Completeness validator with real Python profile ────────────────────


def test_python_with_only_requirements_txt_passes_completeness():
    """Real Python profile: requirements.txt alone satisfies pyproject.toml requirement."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Simulate a typical Flask project generated by Trust5
        open(os.path.join(tmpdir, "requirements.txt"), "w").close()
        open(os.path.join(tmpdir, "app.py"), "w").close()
        open(os.path.join(tmpdir, "test_app.py"), "w").close()

        profile = PROFILES["python"]
        config = QualityConfig()
        validator = ProjectCompletenessValidator(tmpdir, profile, config)
        result = validator.validate()

        assert result.passed is True, (
            f"Python project with requirements.txt should pass completeness. "
            f"Issues: {[i.message for i in result.issues]}"
        )
        assert result.score == 1.0


def test_python_with_no_manifest_passes_completeness():
    """Real Python profile: no required_project_files, so completeness always passes.

    Python's required_files is empty because any manifest (pyproject.toml,
    requirements.txt, setup.py) is acceptable.  Missing manifests are caught
    by other validators (lint, tests), not by the completeness check.
    """
    with tempfile.TemporaryDirectory() as tmpdir:
        open(os.path.join(tmpdir, "app.py"), "w").close()

        profile = PROFILES["python"]
        config = QualityConfig()
        validator = ProjectCompletenessValidator(tmpdir, profile, config)
        result = validator.validate()
        assert result.passed is True
        assert result.score == 1.0


def test_python_with_pyproject_toml_passes_completeness():
    """Real Python profile: pyproject.toml directly passes."""
    with tempfile.TemporaryDirectory() as tmpdir:
        open(os.path.join(tmpdir, "pyproject.toml"), "w").close()

        profile = PROFILES["python"]
        config = QualityConfig()
        validator = ProjectCompletenessValidator(tmpdir, profile, config)
        result = validator.validate()

        assert result.passed is True
        assert result.score == 1.0


# ── finalize_status integration tests ───────────────────────────────


@dataclass
class FakeStage:
    ref_id: str = "test-stage"
    status: object = None
    outputs: dict = field(default_factory=dict)
    error: str = ""


@dataclass
class FakeWorkflow:
    status: object = None
    stages: list = field(default_factory=list)


def test_finalize_detects_spec_compliance_below_threshold():
    """Workflow with spec_compliance_ratio < 1.0 and compliance_passed=False must become TERMINAL."""
    from stabilize.models.status import WorkflowStatus

    from trust5.core.runner import check_stage_failures, finalize_status

    # Simulate: review stage succeeded but compliance is 0.71 (12/17)
    stage = FakeStage(
        ref_id="review",
        status=WorkflowStatus.SUCCEEDED,
        outputs={
            "spec_compliance_ratio": 0.71,
            "spec_criteria_met": 12,
            "spec_criteria_total": 17,
            "compliance_passed": False,
            "spec_unmet_criteria": [
                "Unit tests for all endpoints",
                "Error handling middleware",
                "Database migrations",
            ],
        },
    )
    workflow = FakeWorkflow(status=WorkflowStatus.SUCCEEDED, stages=[stage])

    has_test, has_quality, has_review, has_compliance, details = check_stage_failures(workflow)
    assert has_compliance is True, "compliance_passed=False must be detected"
    assert not has_test
    assert not has_quality

    # Now verify finalize_status actually overrides to TERMINAL
    store = MagicMock()
    with patch("trust5.core.runner.emit"):
        finalize_status(workflow, store)
    assert workflow.status == WorkflowStatus.TERMINAL, (
        f"Workflow with compliance failure should be TERMINAL, got {workflow.status}"
    )
    store.update_status.assert_called_once()


def test_finalize_clean_workflow_stays_succeeded():
    """A workflow with all stages clean should stay SUCCEEDED."""
    from stabilize.models.status import WorkflowStatus

    from trust5.core.runner import finalize_status

    stage = FakeStage(
        ref_id="quality",
        status=WorkflowStatus.SUCCEEDED,
        outputs={"quality_passed": True, "quality_score": 0.92},
    )
    workflow = FakeWorkflow(status=WorkflowStatus.SUCCEEDED, stages=[stage])

    store = MagicMock()
    with patch("trust5.core.runner.emit"):
        finalize_status(workflow, store)
    assert workflow.status == WorkflowStatus.SUCCEEDED
    store.update_status.assert_not_called()


def test_finalize_quality_fail_overrides_to_terminal():
    """Quality gate failure must override SUCCEEDED to TERMINAL."""
    from stabilize.models.status import WorkflowStatus

    from trust5.core.runner import finalize_status

    stage = FakeStage(
        ref_id="quality",
        status=WorkflowStatus.FAILED_CONTINUE,
        outputs={"quality_passed": False, "quality_score": 0.72},
    )
    workflow = FakeWorkflow(status=WorkflowStatus.SUCCEEDED, stages=[stage])

    store = MagicMock()
    with patch("trust5.core.runner.emit"):
        finalize_status(workflow, store)
    assert workflow.status == WorkflowStatus.TERMINAL


def test_finalize_review_fail_overrides_to_terminal():
    """Review failure must override SUCCEEDED to TERMINAL."""
    from stabilize.models.status import WorkflowStatus

    from trust5.core.runner import finalize_status

    stage = FakeStage(
        ref_id="review",
        status=WorkflowStatus.FAILED_CONTINUE,
        outputs={"review_passed": False, "review_score": 0.6},
    )
    workflow = FakeWorkflow(status=WorkflowStatus.SUCCEEDED, stages=[stage])

    store = MagicMock()
    with patch("trust5.core.runner.emit"):
        finalize_status(workflow, store)
    assert workflow.status == WorkflowStatus.TERMINAL


def test_finalize_multiple_failures():
    """Multiple failures (test + quality + compliance) all override to TERMINAL."""
    from stabilize.models.status import WorkflowStatus

    from trust5.core.runner import check_stage_failures, finalize_status

    stages = [
        FakeStage(
            ref_id="validate",
            status=WorkflowStatus.FAILED_CONTINUE,
            outputs={"tests_passed": False, "repair_attempts_used": 5},
        ),
        FakeStage(
            ref_id="quality",
            status=WorkflowStatus.FAILED_CONTINUE,
            outputs={"quality_passed": False, "quality_score": 0.5},
        ),
        FakeStage(
            ref_id="review",
            status=WorkflowStatus.SUCCEEDED,
            outputs={
                "spec_compliance_ratio": 0.7,
                "compliance_passed": False,
                "spec_criteria_met": 7,
                "spec_criteria_total": 10,
            },
        ),
    ]
    workflow = FakeWorkflow(status=WorkflowStatus.SUCCEEDED, stages=stages)

    has_test, has_quality, has_review, has_compliance, details = check_stage_failures(workflow)
    assert has_test is True
    assert has_quality is True
    assert has_compliance is True

    store = MagicMock()
    with patch("trust5.core.runner.emit"):
        finalize_status(workflow, store)
    assert workflow.status == WorkflowStatus.TERMINAL


# ── Cross-language profile required_files tests ────────────────────────


@pytest.mark.parametrize("lang", ["python", "go", "typescript", "javascript", "rust"])
def test_profile_required_files_are_subset_of_manifests_or_standalone(lang):
    """Required project files should either be in manifest_files or be standalone.

    This ensures the manifest fallback logic works correctly for all profiles.
    """
    profile = PROFILES[lang]
    for req_file in profile.required_project_files:
        # A required file either IS a manifest (eligible for fallback)
        # or it's standalone (must actually exist). Both are valid.
        # We just verify it's a known file in the profile.
        assert isinstance(req_file, str)
        assert len(req_file) > 0
