"""GitLab CI linter — 10 rules for pipeline best practices."""

from __future__ import annotations

from ..models import Finding, LintRule, Severity

RULES: dict[str, LintRule] = {
    "GL001": LintRule("GL001", "Security", Severity.HIGH, "Secrets in script", "Hardcoded secrets in script commands", "Use CI/CD variables ($CI_* or protected variables)"),
    "GL002": LintRule("GL002", "Reliability", Severity.MEDIUM, "No retry configuration", "Job has no retry policy for flaky steps", "Add retry: 2 or retry: { max: 2, when: runner_system_failure }"),
    "GL003": LintRule("GL003", "Best Practice", Severity.LOW, "No cache configuration", "Pipeline has no cache for dependencies", "Add cache with key and paths for node_modules, .venv, etc."),
    "GL004": LintRule("GL004", "Security", Severity.MEDIUM, "Using :latest image tag", "Job image uses :latest or no tag", "Pin image to specific version"),
    "GL005": LintRule("GL005", "Reliability", Severity.MEDIUM, "No timeout set", "Job has no timeout configured", "Add timeout: 30m to prevent hung jobs"),
    "GL006": LintRule("GL006", "Best Practice", Severity.INFO, "No stages defined", "Pipeline has no explicit stages", "Define stages for clear pipeline structure"),
    "GL007": LintRule("GL007", "Security", Severity.HIGH, "allow_failure on security jobs", "Security-related job has allow_failure: true", "Security jobs should not allow failures"),
    "GL008": LintRule("GL008", "Best Practice", Severity.LOW, "No artifacts configuration", "Job produces no artifacts", "Add artifacts for test reports, coverage, builds"),
    "GL009": LintRule("GL009", "Reliability", Severity.MEDIUM, "No rules/only/except", "Job has no execution rules (runs on every pipeline)", "Add rules or only/except to control when jobs run"),
    "GL010": LintRule("GL010", "Best Practice", Severity.LOW, "Missing job description", "Job has no description or comments", "Add a clear description for each job stage"),
}

_RESERVED_KEYS = {
    "stages", "variables", "default", "include", "workflow",
    "image", "services", "before_script", "after_script", "cache",
}

_SECRET_PATTERNS = [
    "password", "secret", "token", "api_key", "private_key",
    "aws_secret", "access_key", "ssh_key",
]


def lint_gitlab_ci(docs: list[dict], file_path: str = "") -> list[Finding]:
    """Lint GitLab CI pipeline files."""
    findings: list[Finding] = []

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

        # GL006: No stages
        if "stages" not in doc:
            findings.append(Finding(rule=RULES["GL006"], line=0, message="No explicit stages defined"))

        # GL003: No cache (top-level)
        has_cache = "cache" in doc

        # Iterate jobs (keys that aren't reserved)
        jobs_found = 0
        for key, job in doc.items():
            if key.startswith(".") or key in _RESERVED_KEYS:
                continue
            if not isinstance(job, dict):
                continue
            if "script" not in job and "trigger" not in job and "extends" not in job:
                continue

            jobs_found += 1
            prefix = f"job '{key}'"

            # GL004: :latest image
            image = job.get("image", "")
            if isinstance(image, dict):
                image = image.get("name", "")
            if image and (":" not in image or image.endswith(":latest")):
                findings.append(Finding(rule=RULES["GL004"], line=0, message=f"{prefix}: Image '{image}' uses :latest or no tag"))

            # GL001: Secrets in script
            scripts = job.get("script", [])
            if isinstance(scripts, str):
                scripts = [scripts]
            for s in scripts:
                s_lower = str(s).lower()
                for pat in _SECRET_PATTERNS:
                    if pat in s_lower and "=" in str(s):
                        findings.append(Finding(rule=RULES["GL001"], line=0, message=f"{prefix}: Potential hardcoded secret in script"))
                        break

            # GL002: No retry
            if not job.get("retry"):
                findings.append(Finding(rule=RULES["GL002"], line=0, message=f"{prefix}: No retry configuration"))

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

            # GL007: allow_failure on security jobs
            if job.get("allow_failure"):
                stage = job.get("stage", "").lower()
                job_key = key.lower()
                check_str = f"{stage} {job_key}"
                if any(kw in check_str for kw in ("security", "scan", "sast", "dast", "audit")):
                    findings.append(Finding(rule=RULES["GL007"], line=0, message=f"{prefix}: Security job has allow_failure: true"))

            # GL008: No artifacts
            if not job.get("artifacts"):
                findings.append(Finding(rule=RULES["GL008"], line=0, message=f"{prefix}: No artifacts configured"))

            # GL009: No rules/only/except
            if not job.get("rules") and not job.get("only") and not job.get("except"):
                findings.append(Finding(rule=RULES["GL009"], line=0, message=f"{prefix}: No execution rules defined"))

            # GL003: No cache (per-job if not global)
            if not has_cache and not job.get("cache"):
                pass  # Only flag once globally

        if not has_cache and jobs_found > 0:
            findings.append(Finding(rule=RULES["GL003"], line=0, message="No cache configuration in pipeline"))

    return findings
