"""GitHub Actions linter — 10 rules for workflow best practices."""

from __future__ import annotations

from ..models import Finding, LintRule, Severity

RULES: dict[str, LintRule] = {
    "GHA001": LintRule("GHA001", "Security", Severity.CRITICAL, "Using @master/main branch ref", "Action pinned to mutable branch ref instead of SHA", "Pin actions to a full commit SHA"),
    "GHA002": LintRule("GHA002", "Security", Severity.HIGH, "Checkout with persist-credentials", "actions/checkout defaults persist-credentials to true", "Set persist-credentials: false"),
    "GHA003": LintRule("GHA003", "Security", Severity.HIGH, "Secrets in run step", "Secrets interpolated in shell commands (visible in logs)", "Set secrets as env vars instead of inline ${{ secrets.* }}"),
    "GHA004": LintRule("GHA004", "Security", Severity.MEDIUM, "Broad permissions", "Workflow or job has write-all or broad permissions", "Use least-privilege permissions (contents: read)"),
    "GHA005": LintRule("GHA005", "Reliability", Severity.MEDIUM, "No timeout-minutes", "Job has no timeout-minutes set (default: 360 = 6 hours)", "Add timeout-minutes to prevent hung jobs"),
    "GHA006": LintRule("GHA006", "Security", Severity.MEDIUM, "pull_request_target trigger", "Using pull_request_target can expose secrets to forks", "Use pull_request trigger instead when possible"),
    "GHA007": LintRule("GHA007", "Best Practice", Severity.LOW, "No concurrency group", "Workflow has no concurrency control", "Add concurrency group to prevent duplicate runs"),
    "GHA008": LintRule("GHA008", "Best Practice", Severity.INFO, "Unversioned runner", "Using 'latest' runner like ubuntu-latest", "Pin to specific runner version (e.g., ubuntu-22.04)"),
    "GHA009": LintRule("GHA009", "Security", Severity.HIGH, "Script injection risk", "Using github.event context in run step (injection risk)", "Use intermediate env var instead of direct interpolation"),
    "GHA010": LintRule("GHA010", "Best Practice", Severity.LOW, "Missing workflow name", "Workflow has no 'name' field", "Add a descriptive name for the workflow"),
}

_INJECTION_PATTERNS = [
    "github.event.issue.title",
    "github.event.issue.body",
    "github.event.pull_request.title",
    "github.event.pull_request.body",
    "github.event.comment.body",
    "github.event.review.body",
    "github.event.head_commit.message",
    "github.event.commits",
    "github.head_ref",
]


def lint_github_actions(docs: list[dict], file_path: str = "") -> list[Finding]:
    """Lint GitHub Actions workflow files."""
    findings: list[Finding] = []

    for doc in docs:
        if not isinstance(doc, dict):
            continue

        # GHA010: Missing name
        if not doc.get("name"):
            findings.append(Finding(rule=RULES["GHA010"], line=0, message="Workflow missing 'name' field"))

        # GHA006: pull_request_target
        on_trigger = doc.get("on", doc.get(True, {}))
        if isinstance(on_trigger, dict):
            if "pull_request_target" in on_trigger:
                findings.append(Finding(rule=RULES["GHA006"], line=0, message="Using pull_request_target trigger — may expose secrets to forks"))
        elif isinstance(on_trigger, list):
            if "pull_request_target" in on_trigger:
                findings.append(Finding(rule=RULES["GHA006"], line=0, message="Using pull_request_target trigger — may expose secrets to forks"))

        # GHA004: Top-level permissions
        perms = doc.get("permissions", {})
        if isinstance(perms, str) and perms == "write-all":
            findings.append(Finding(rule=RULES["GHA004"], line=0, message="Workflow has 'write-all' permissions"))

        # GHA007: No concurrency
        if not doc.get("concurrency"):
            findings.append(Finding(rule=RULES["GHA007"], line=0, message="No concurrency group defined"))

        jobs = doc.get("jobs", {}) or {}
        for job_name, job in jobs.items():
            if not isinstance(job, dict):
                continue
            prefix = f"jobs.{job_name}"

            # GHA005: No timeout
            if not job.get("timeout-minutes"):
                findings.append(Finding(rule=RULES["GHA005"], line=0, message=f"{prefix}: No timeout-minutes set"))

            # GHA008: Latest runner
            runs_on = job.get("runs-on", "")
            if isinstance(runs_on, str) and "latest" in runs_on:
                findings.append(Finding(rule=RULES["GHA008"], line=0, message=f"{prefix}: Using '{runs_on}' — pin to specific version"))

            # GHA004: Job-level permissions
            job_perms = job.get("permissions", {})
            if isinstance(job_perms, str) and job_perms == "write-all":
                findings.append(Finding(rule=RULES["GHA004"], line=0, message=f"{prefix}: Job has 'write-all' permissions"))

            steps = job.get("steps", []) or []
            for i, step in enumerate(steps):
                if not isinstance(step, dict):
                    continue
                step_name = step.get("name", f"step-{i + 1}")
                step_prefix = f"{prefix}.{step_name}"

                uses = step.get("uses", "")
                if uses:
                    # GHA001: Mutable ref
                    if "@" in uses:
                        ref = uses.split("@")[-1]
                        if ref in ("master", "main", "develop") or (len(ref) < 12 and not ref.startswith("v")):
                            findings.append(Finding(rule=RULES["GHA001"], line=0, message=f"{step_prefix}: Action '{uses}' pinned to mutable ref"))
                    else:
                        findings.append(Finding(rule=RULES["GHA001"], line=0, message=f"{step_prefix}: Action '{uses}' has no version pin"))

                    # GHA002: checkout persist-credentials
                    if "actions/checkout" in uses:
                        with_block = step.get("with", {}) or {}
                        pc = with_block.get("persist-credentials")
                        if pc is not False:
                            if pc is True:
                                findings.append(Finding(rule=RULES["GHA002"], line=0, message=f"{step_prefix}: actions/checkout with persist-credentials: true"))
                            elif "persist-credentials" not in with_block:
                                findings.append(Finding(rule=RULES["GHA002"], line=0, message=f"{step_prefix}: actions/checkout with default persist-credentials"))

                run_cmd = step.get("run", "")
                if run_cmd:
                    # GHA003: Secrets in run
                    if "${{ secrets." in run_cmd:
                        findings.append(Finding(rule=RULES["GHA003"], line=0, message=f"{step_prefix}: Secret interpolated directly in run command"))

                    # GHA009: Script injection
                    for pattern in _INJECTION_PATTERNS:
                        if pattern in run_cmd:
                            findings.append(Finding(rule=RULES["GHA009"], line=0, message=f"{step_prefix}: Potential script injection via {pattern}"))
                            break

    return findings
