from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, List

import yaml

from vibegate.config import OutputsConfig
from vibegate.findings import Finding


@dataclass(frozen=True)
class ArtifactRecord:
    name: str
    path: Path
    sha256: str


def sha256_path(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(8192), b""):
            digest.update(chunk)
    return digest.hexdigest()


def _ensure_parent(path: Path) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)


def write_report(
    outputs: OutputsConfig,
    status: str,
    findings: List[Finding],
    skipped_checks: List[dict[str, str]],
) -> ArtifactRecord:
    _ensure_parent(outputs.report_markdown)
    lines = ["# VibeGate Report", "", f"Status: {status}", "", "## Findings", ""]
    if not findings:
        lines.append("No findings detected.")
    else:
        for finding in findings:
            location = ""
            if finding.location and finding.location.path:
                location = f" ({finding.location.path})"
            lines.append(
                f"- [{finding.severity}] {finding.check_id} {finding.rule_id}{location}: {finding.message}"
            )
    if skipped_checks:
        lines.extend(["", "## Skipped Checks", ""])
        for skipped in skipped_checks:
            check_id = skipped.get("check_id", "unknown")
            reason = skipped.get("reason", "unknown")
            lines.append(f"- {check_id}: {reason}")
    outputs.report_markdown.write_text("\n".join(lines) + "\n", encoding="utf-8")
    return ArtifactRecord(
        name="report_markdown",
        path=outputs.report_markdown,
        sha256=sha256_path(outputs.report_markdown),
    )


def write_report_html(outputs: OutputsConfig) -> ArtifactRecord:
    _ensure_parent(outputs.report_html)
    markdown = outputs.report_markdown.read_text(encoding="utf-8")
    html = "<pre>" + markdown.replace("&", "&amp;").replace("<", "&lt;") + "</pre>\n"
    outputs.report_html.write_text(html, encoding="utf-8")
    return ArtifactRecord(
        name="report_html",
        path=outputs.report_html,
        sha256=sha256_path(outputs.report_html),
    )


def _severity_rank(severity: str) -> int:
    order = {"critical": 5, "high": 4, "medium": 3, "low": 2, "info": 1}
    return order.get(severity, 0)


def _finding_path(finding: Finding) -> str:
    if finding.location and finding.location.path:
        return finding.location.path
    return ""


def _ordering_key(finding: Finding) -> tuple:
    return (
        -_severity_rank(finding.severity),
        _finding_path(finding),
        finding.rule_id,
        finding.fingerprint,
    )


TASK_TYPE_ORDER = [
    "dependency_fix",
    "vulnerability_fix",
    "secret_removal",
    "security_fix",
    "type_fix",
    "lint_fix",
    "formatting_fix",
    "test_fix",
    "config_fix",
    "runtime_fix",
]

FINDING_TO_TASK_TYPE = {
    "dependency_hygiene": "dependency_fix",
    "vulnerability": "vulnerability_fix",
    "secrets": "secret_removal",
    "sast": "security_fix",
    "typecheck": "type_fix",
    "lint": "lint_fix",
    "formatting": "formatting_fix",
    "tests": "test_fix",
    "config_sanity": "config_fix",
    "runtime_smoke": "runtime_fix",
}

GROUP_TITLES = {
    "dependency_fix": "Dependency hygiene fixes",
    "vulnerability_fix": "Vulnerability fixes",
    "secret_removal": "Secret removals",
    "security_fix": "Security fixes",
    "type_fix": "Typecheck fixes",
    "lint_fix": "Lint fixes",
    "formatting_fix": "Formatting fixes",
    "test_fix": "Test fixes",
    "config_fix": "Config sanity fixes",
    "runtime_fix": "Runtime smoke fixes",
}


def _task_title(finding: Finding, task_type: str) -> str:
    base = finding.message
    if len(base) > 80:
        base = base[:77] + "..."
    return f"{task_type.replace('_', ' ').title()}: {base}"


def _verification_commands(finding: Finding) -> List[str]:
    commands = ["vibegate check"]
    if finding.finding_type == "lint":
        commands.append("ruff check --output-format json .")
    elif finding.finding_type == "formatting":
        commands.append("ruff format --check .")
    elif finding.finding_type == "typecheck":
        commands.append("pyright --outputjson .")
    elif finding.finding_type == "tests":
        commands.append("pytest -q")
    return commands


def build_fixpack(
    run_id: str,
    findings: List[Finding],
    contract_sha: str,
    evidence_sha: str,
) -> dict[str, Any]:
    grouped: dict[str, List[Finding]] = {task_type: [] for task_type in TASK_TYPE_ORDER}
    for finding in findings:
        task_type = FINDING_TO_TASK_TYPE.get(finding.finding_type)
        if task_type:
            grouped[task_type].append(finding)

    groups: List[dict[str, Any]] = []
    task_order = 1
    group_order = 1

    for task_type in TASK_TYPE_ORDER:
        items = sorted(grouped.get(task_type, []), key=_ordering_key)
        if not items:
            continue
        tasks: List[dict[str, Any]] = []
        for finding in items:
            file_targets = []
            if finding.location and finding.location.path:
                file_targets.append(finding.location.path)
            tasks.append(
                {
                    "id": f"{task_type}-{task_order}",
                    "type": task_type,
                    "order": task_order,
                    "title": _task_title(finding, task_type),
                    "description": finding.message,
                    "file_targets": file_targets,
                    "references": [finding.fingerprint],
                    "depends_on": [],
                    "acceptance_criteria": [
                        "The underlying issue is resolved without regressions.",
                        "Relevant checks report no remaining findings.",
                    ],
                    "verification_commands": _verification_commands(finding),
                }
            )
            task_order += 1
        groups.append(
            {
                "id": f"{task_type}_group",
                "title": GROUP_TITLES.get(
                    task_type, task_type.replace("_", " ").title()
                ),
                "order": group_order,
                "tasks": tasks,
            }
        )
        group_order += 1

    return {
        "schema_version": "v1alpha1",
        "run_id": run_id,
        "generated_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
        "source": {"contract_sha256": contract_sha, "evidence_sha256": evidence_sha},
        "groups": groups,
    }


def write_fixpack(
    outputs: OutputsConfig, fixpack: dict[str, Any], result: str
) -> List[ArtifactRecord]:
    records: List[ArtifactRecord] = []
    _ensure_parent(outputs.fixpack_json)
    outputs.fixpack_json.write_text(
        json.dumps(fixpack, indent=2, sort_keys=True) + "\n", encoding="utf-8"
    )
    records.append(
        ArtifactRecord(
            "fixpack_json", outputs.fixpack_json, sha256_path(outputs.fixpack_json)
        )
    )

    _ensure_parent(outputs.fixpack_md)
    outputs.fixpack_md.write_text(_fixpack_markdown(fixpack, result), encoding="utf-8")
    records.append(
        ArtifactRecord(
            "fixpack_md", outputs.fixpack_md, sha256_path(outputs.fixpack_md)
        )
    )

    if outputs.emit_fixpack_yaml:
        _ensure_parent(outputs.fixpack_yaml)
        outputs.fixpack_yaml.write_text(
            yaml.safe_dump(fixpack, sort_keys=False), encoding="utf-8"
        )
        records.append(
            ArtifactRecord(
                "fixpack_yaml", outputs.fixpack_yaml, sha256_path(outputs.fixpack_yaml)
            )
        )

    return records


def _fixpack_markdown(fixpack: dict[str, Any], result: str) -> str:
    run_id = fixpack.get("run_id", "unknown")
    groups = fixpack.get("groups", [])
    total_tasks = sum(len(group.get("tasks", [])) for group in groups)
    lines = [
        f"Fix Pack for run {run_id}",
        "",
        "Summary",
        "",
        f"PASS/FAIL: {result}",
        f"Task count: {total_tasks}",
        "Ordering: Apply tasks in order.",
        "",
    ]
    for group in sorted(groups, key=lambda g: g.get("order", 0)):
        lines.append(f"## {group.get('title', 'Untitled')}")
        tasks = group.get("tasks", [])
        for task in sorted(tasks, key=lambda t: t.get("order", 0)):
            lines.append("")
            lines.append(
                f"[{task.get('order', 0)}] {task.get('title', 'Untitled')} ({task.get('type', 'unknown')})"
            )
            lines.append("Files:")
            file_targets = task.get("file_targets", [])
            if file_targets:
                for path in file_targets:
                    lines.append(f"- {path}")
            else:
                lines.append("- (none)")
            lines.append("Why:")
            lines.append(task.get("description", ""))
            lines.append("Steps:")
            lines.append("- Address the issue described above.")
            lines.append("Acceptance criteria:")
            for item in task.get("acceptance_criteria", []):
                lines.append(f"- {item}")
            lines.append("Verification commands:")
            for cmd in task.get("verification_commands", []):
                lines.append(f"- {cmd}")
        lines.append("")
    return "\n".join(lines).strip() + "\n"
