"""Requirement Analyst, Solution Architect, and orchestrator flow tests."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from packages.agents.requirement_analyst import RequirementAnalyst
from packages.agents.solution_architect import SolutionArchitect
from packages.artifacts.design import DesignArtifact, MetadataPlanItem, enforce_allowlist
from packages.db.models import WorkflowState
from packages.orchestrator.graph import Orchestrator, create_run
from tests.conftest import load_story


@pytest.mark.asyncio
async def test_analyst_clear_story(artifacts_tmpdir):
    issue = load_story("SALES-101")
    artifact = await RequirementAnalyst().analyze(issue)
    assert artifact.jira_key == "SALES-101"
    assert artifact.is_clear is True
    assert artifact.confidence >= 0.5
    assert artifact.clarification_questions == []


@pytest.mark.asyncio
async def test_analyst_unclear_story(artifacts_tmpdir):
    issue = load_story("SALES-102")
    artifact = await RequirementAnalyst().analyze(issue)
    assert artifact.is_clear is False
    assert len(artifact.clarification_questions) >= 1
    body = artifact.clarification_comment_body()
    assert "1." in body


@pytest.mark.asyncio
async def test_solution_architect_for_clear_story(artifacts_tmpdir):
    issue = load_story("SALES-101")
    requirements = await RequirementAnalyst().analyze(issue)
    assert requirements.is_clear is True
    design = await SolutionArchitect().design(requirements)
    assert design.jira_key == "SALES-101"
    assert design.metadata_plan
    assert any(i.metadata_type == "CustomField" for i in design.metadata_plan)
    assert design.out_of_allowlist is False


def test_enforce_allowlist_strips_blocked_and_destructive():
    design = DesignArtifact(
        jira_key="SALES-101",
        summary="Test",
        approach="Test approach",
        metadata_plan=[
            MetadataPlanItem(
                metadata_type="CustomField",
                api_name="Ok__c",
                action="create",
                object="Account",
            ),
            MetadataPlanItem(
                metadata_type="Profile",
                api_name="Admin",
                action="update",
            ),
            MetadataPlanItem(
                metadata_type="CustomField",
                api_name="Gone__c",
                action="delete",
                object="Account",
            ),
            MetadataPlanItem(
                metadata_type="lwc",
                api_name="accountCard",
                action="create",
            ),
        ],
    )
    cleaned = enforce_allowlist(design)
    types = {i.metadata_type for i in cleaned.metadata_plan}
    assert types == {"CustomField", "LightningComponentBundle"}
    assert cleaned.out_of_allowlist is True
    assert any("Profile" in r for r in cleaned.risks_or_escalations)
    assert any("delete" in r for r in cleaned.risks_or_escalations)


@pytest.mark.asyncio
async def test_happy_path_to_design_review(db_session, mock_jira, artifacts_tmpdir):
    issue = load_story("SALES-101")
    mock_jira.seed(issue)
    run = await create_run(db_session, issue.key)
    orch = Orchestrator(db_session, jira=mock_jira)
    result = await orch.run(run.id)
    assert result.state == WorkflowState.DESIGN_REVIEW
    req_path = Path(result.artifacts["requirements.json"])
    assert req_path.exists()
    data = json.loads(req_path.read_text(encoding="utf-8"))
    assert data["is_clear"] is True
    design_path = Path(result.artifacts["design.json"])
    assert design_path.exists()
    design = json.loads(design_path.read_text(encoding="utf-8"))
    assert design["metadata_plan"]
    assert design["approach"]
    assert any("Agent Nova design ready for review" in c["body"] for c in mock_jira.comments_posted)
    assert result.artifacts.get("confluence_url")
    assert any("Confluence design doc" in c["body"] for c in mock_jira.comments_posted)


@pytest.mark.asyncio
async def test_clarification_pause_and_resume(db_session, mock_jira, artifacts_tmpdir):
    issue = load_story("SALES-102")
    mock_jira.seed(issue)
    run = await create_run(db_session, issue.key)
    orch = Orchestrator(db_session, jira=mock_jira)
    paused = await orch.run(run.id)
    assert paused.state == WorkflowState.NEEDS_CLARIFICATION
    assert any("Agent Nova needs clarification" in c["body"] for c in mock_jira.comments_posted)

    # Human answers — update issue and resume
    clarified = load_story("SALES-110")
    clarified.key = "SALES-102"
    mock_jira.seed(clarified)
    resumed = await orch.run(run.id, resume=True)
    assert resumed.state == WorkflowState.DESIGN_REVIEW
    assert Path(resumed.artifacts["design.json"]).exists()


@pytest.mark.asyncio
async def test_design_review_pause_and_approve(db_session, mock_jira, artifacts_tmpdir):
    issue = load_story("SALES-101")
    mock_jira.seed(issue)
    run = await create_run(db_session, issue.key)
    orch = Orchestrator(db_session, jira=mock_jira)
    paused = await orch.run(run.id)
    assert paused.state == WorkflowState.DESIGN_REVIEW

    arts = dict(paused.artifacts or {})
    arts["design_decision"] = "approve"
    arts["design_resume_comment"] = "Looks good, approve"
    paused.artifacts = arts
    await db_session.flush()

    approved = await orch.run(run.id, resume=True)
    assert approved.state == WorkflowState.PR_CREATED
    assert approved.artifacts.get("design_approved") is True
    assert approved.branch_name == "agent-nova/sales-101"
    assert approved.pr_url
    assert Path(approved.artifacts["implementation.json"]).exists()
    assert Path(approved.artifacts["deploy.json"]).exists()
    assert Path(approved.artifacts["review.json"]).exists()
    assert Path(approved.artifacts["qa-report.json"]).exists()
    assert Path(approved.artifacts["pmd.json"]).exists()
    assert any("draft PR" in c["body"] for c in mock_jira.comments_posted)


@pytest.mark.asyncio
async def test_design_review_reject(db_session, mock_jira, artifacts_tmpdir):
    issue = load_story("SALES-101")
    mock_jira.seed(issue)
    run = await create_run(db_session, issue.key)
    orch = Orchestrator(db_session, jira=mock_jira)
    paused = await orch.run(run.id)
    assert paused.state == WorkflowState.DESIGN_REVIEW

    arts = dict(paused.artifacts or {})
    arts["design_decision"] = "reject"
    arts["design_resume_comment"] = "reject — wrong object"
    paused.artifacts = arts
    await db_session.flush()

    clarifying = await orch.run(run.id, resume=True)
    assert clarifying.state == WorkflowState.NEEDS_CLARIFICATION
    assert clarifying.artifacts.get("design_reject_hint") == "wrong object"
    assert clarifying.artifacts.get("design_revision") == 1
    assert clarifying.artifacts.get("design_decision") is None
    assert any(
        "previous design was rejected" in c["body"].lower() for c in mock_jira.comments_posted
    )
    assert any("wrong object" in c["body"] for c in mock_jira.comments_posted)

    # Human provides clarification → redesign → DESIGN_REVIEW again
    issue.comments.append(
        {
            "author": "human",
            "body": "Use Contact instead of Account for the Customer Tier field.",
            "account_id": "human-1",
        }
    )
    mock_jira.seed(issue)
    redesigned = await orch.run(run.id, resume=True)
    assert redesigned.state == WorkflowState.DESIGN_REVIEW
    assert Path(redesigned.artifacts["design.json"]).exists()
    assert redesigned.artifacts.get("design_approved") is not True


@pytest.mark.asyncio
async def test_design_review_reject_without_hint(db_session, mock_jira, artifacts_tmpdir):
    issue = load_story("SALES-101")
    mock_jira.seed(issue)
    run = await create_run(db_session, issue.key)
    orch = Orchestrator(db_session, jira=mock_jira)
    paused = await orch.run(run.id)
    assert paused.state == WorkflowState.DESIGN_REVIEW

    arts = dict(paused.artifacts or {})
    arts["design_decision"] = "reject"
    arts["design_resume_comment"] = "rejected"
    paused.artifacts = arts
    await db_session.flush()

    clarifying = await orch.run(run.id, resume=True)
    assert clarifying.state == WorkflowState.NEEDS_CLARIFICATION
    assert clarifying.artifacts.get("design_reject_hint") == ""
    assert any("Agent Nova needs clarification" in c["body"] for c in mock_jira.comments_posted)


@pytest.mark.asyncio
async def test_design_review_confluence_soft_fail(db_session, mock_jira, artifacts_tmpdir):
    class FailingConfluence:
        async def create_page(self, space_key: str, title: str, body_storage: str):
            raise RuntimeError("space forbidden")

    issue = load_story("SALES-101")
    mock_jira.seed(issue)
    run = await create_run(db_session, issue.key)
    orch = Orchestrator(db_session, jira=mock_jira, confluence=FailingConfluence())
    paused = await orch.run(run.id)
    assert paused.state == WorkflowState.DESIGN_REVIEW
    assert not paused.artifacts.get("confluence_url")
    assert any(
        "Confluence design doc" in c["body"]
        and "Not created" in c["body"]
        and "Agent Nova design ready for review" in c["body"]
        for c in mock_jira.comments_posted
    )


@pytest.mark.asyncio
async def test_design_review_approve_deploy_failure(db_session, mock_jira, artifacts_tmpdir):
    from packages.integrations.salesforce import DeployResult

    class FailingDeployer:
        async def deploy_validate(self, *, source_dir, jira_key):
            return DeployResult(
                success=False,
                check_only=True,
                status="Failed",
                message="Component failures in sandbox",
            )

    issue = load_story("SALES-101")
    mock_jira.seed(issue)
    run = await create_run(db_session, issue.key)
    orch = Orchestrator(db_session, jira=mock_jira, deployer=FailingDeployer())
    paused = await orch.run(run.id)
    assert paused.state == WorkflowState.DESIGN_REVIEW

    arts = dict(paused.artifacts or {})
    arts["design_decision"] = "approve"
    arts["design_resume_comment"] = "approve"
    paused.artifacts = arts
    await db_session.flush()

    failed = await orch.run(run.id, resume=True)
    assert failed.state == WorkflowState.FAILED
    assert "deploy validate failed" in (failed.error or "").lower()
    assert Path(failed.artifacts["deploy.json"]).exists()


@pytest.mark.asyncio
async def test_review_fail_then_fix_then_pr(db_session, mock_jira, artifacts_tmpdir):
    from packages.artifacts.review import ReviewArtifact, ReviewFinding

    class FlakyReviewer:
        def __init__(self) -> None:
            self.calls = 0

        async def review(self, **kwargs):
            self.calls += 1
            run_id = kwargs["run_id"]
            from packages.db.artifacts import ArtifactStore

            store = ArtifactStore()
            if self.calls == 1:
                artifact = ReviewArtifact(
                    jira_key=kwargs["requirements"].jira_key,
                    passed=False,
                    cycle=kwargs["cycle"],
                    findings=[
                        ReviewFinding(
                            severity="high",
                            category="test_coverage",
                            file="force-app/main/default/classes/AccountHandler.cls",
                            message="Missing tests",
                            fixable=True,
                        )
                    ],
                    summary="Review failed",
                    static_analysis_status="passed",
                )
            else:
                artifact = ReviewArtifact(
                    jira_key=kwargs["requirements"].jira_key,
                    passed=True,
                    cycle=kwargs["cycle"],
                    findings=[],
                    summary="Review passed",
                    static_analysis_status="passed",
                )
            store.write_json(run_id, "review.json", artifact)
            store.write_json(run_id, f"review-cycle-{kwargs['cycle']}.json", artifact)
            return artifact

    issue = load_story("SALES-101")
    mock_jira.seed(issue)
    run = await create_run(db_session, issue.key)
    orch = Orchestrator(db_session, jira=mock_jira, reviewer=FlakyReviewer())
    paused = await orch.run(run.id)
    assert paused.state == WorkflowState.DESIGN_REVIEW

    arts = dict(paused.artifacts or {})
    arts["design_decision"] = "approve"
    arts["design_resume_comment"] = "approve"
    paused.artifacts = arts
    await db_session.flush()

    approved = await orch.run(run.id, resume=True)
    assert approved.state == WorkflowState.PR_CREATED
    assert int(approved.artifacts.get("review_cycle", 0)) >= 2


@pytest.mark.asyncio
async def test_review_max_cycles_failed(db_session, mock_jira, artifacts_tmpdir):
    from packages.artifacts.review import ReviewArtifact, ReviewFinding

    class AlwaysFailReviewer:
        async def review(self, **kwargs):
            from packages.db.artifacts import ArtifactStore

            store = ArtifactStore()
            artifact = ReviewArtifact(
                jira_key=kwargs["requirements"].jira_key,
                passed=False,
                cycle=kwargs["cycle"],
                findings=[
                    ReviewFinding(
                        severity="high",
                        category="security",
                        message="Always fails",
                        fixable=True,
                    )
                ],
                summary="Always failing review",
                static_analysis_status="passed",
            )
            store.write_json(kwargs["run_id"], "review.json", artifact)
            return artifact

    issue = load_story("SALES-101")
    mock_jira.seed(issue)
    run = await create_run(db_session, issue.key)
    orch = Orchestrator(db_session, jira=mock_jira, reviewer=AlwaysFailReviewer())
    paused = await orch.run(run.id)
    arts = dict(paused.artifacts or {})
    arts["design_decision"] = "approve"
    paused.artifacts = arts
    await db_session.flush()

    failed = await orch.run(run.id, resume=True)
    assert failed.state == WorkflowState.FAILED
    assert "review" in (failed.error or "").lower()


@pytest.mark.asyncio
async def test_qa_fail_blocks_pr(db_session, mock_jira, artifacts_tmpdir):
    from packages.artifacts.qa_report import QAReportArtifact

    class FailingQA:
        async def run_qa(self, **kwargs):
            from packages.db.artifacts import ArtifactStore

            store = ArtifactStore()
            artifact = QAReportArtifact(
                jira_key=kwargs["requirements"].jira_key,
                passed=False,
                test_matrix=[],
                summary="Mandatory tests failed",
            )
            store.write_json(kwargs["run_id"], "qa-report.json", artifact)
            return artifact

    issue = load_story("SALES-101")
    mock_jira.seed(issue)
    run = await create_run(db_session, issue.key)
    orch = Orchestrator(db_session, jira=mock_jira, qa=FailingQA())
    paused = await orch.run(run.id)
    arts = dict(paused.artifacts or {})
    arts["design_decision"] = "approve"
    paused.artifacts = arts
    await db_session.flush()

    failed = await orch.run(run.id, resume=True)
    assert failed.state == WorkflowState.FAILED
    assert "qa" in (failed.error or "").lower()
    assert not failed.pr_url
