#!/usr/bin/env python3
"""Detect project stack/tooling signals for CI/CD pipeline generation.

Input sources:
- repository scan via --repo
- JSON via --input file
- JSON via stdin

Output:
- text summary or JSON payload
"""

import argparse
import json
import sys
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Dict, List, Optional


class CLIError(Exception):
    """Raised for expected CLI failures."""


@dataclass
class StackReport:
    repo: str
    languages: List[str]
    package_managers: List[str]
    ci_targets: List[str]
    test_commands: List[str]
    build_commands: List[str]
    lint_commands: List[str]
    signals: Dict[str, bool]


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Detect stack/tooling from a repository.")
    parser.add_argument("--input", help="JSON input file (precomputed signal payload).")
    parser.add_argument("--repo", default=".", help="Repository path to scan.")
    parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format.")
    return parser.parse_args()


def load_payload(input_path: Optional[str]) -> Optional[dict]:
    if input_path:
        try:
            return json.loads(Path(input_path).read_text(encoding="utf-8"))
        except Exception as exc:
            raise CLIError(f"Failed reading --input file: {exc}") from exc

    if not sys.stdin.isatty():
        raw = sys.stdin.read().strip()
        if raw:
            try:
                return json.loads(raw)
            except json.JSONDecodeError as exc:
                raise CLIError(f"Invalid JSON from stdin: {exc}") from exc

    return None


def read_package_scripts(repo: Path) -> Dict[str, str]:
    pkg = repo / "package.json"
    if not pkg.exists():
        return {}
    try:
        data = json.loads(pkg.read_text(encoding="utf-8"))
    except Exception:
        return {}
    scripts = data.get("scripts", {})
    return scripts if isinstance(scripts, dict) else {}


def detect(repo: Path) -> StackReport:
    signals = {
        "package_json": (repo / "package.json").exists(),
        "pnpm_lock": (repo / "pnpm-lock.yaml").exists(),
        "yarn_lock": (repo / "yarn.lock").exists(),
        "npm_lock": (repo / "package-lock.json").exists(),
        "pyproject": (repo / "pyproject.toml").exists(),
        "requirements": (repo / "requirements.txt").exists(),
        "go_mod": (repo / "go.mod").exists(),
        "dockerfile": (repo / "Dockerfile").exists(),
        "vercel": (repo / "vercel.json").exists(),
        "helm": (repo / "helm").exists() or (repo / "charts").exists(),
        "k8s": (repo / "k8s").exists() or (repo / "kubernetes").exists(),
    }

    languages: List[str] = []
    package_managers: List[str] = []
    ci_targets: List[str] = ["github", "gitlab"]

    if signals["package_json"]:
        languages.append("node")
        if signals["pnpm_lock"]:
            package_managers.append("pnpm")
        elif signals["yarn_lock"]:
            package_managers.append("yarn")
        else:
            package_managers.append("npm")

    if signals["pyproject"] or signals["requirements"]:
        languages.append("python")
        package_managers.append("pip")

    if signals["go_mod"]:
        languages.append("go")

    scripts = read_package_scripts(repo)
    lint_commands: List[str] = []
    test_commands: List[str] = []
    build_commands: List[str] = []

    if "lint" in scripts:
        lint_commands.append("npm run lint")
    if "test" in scripts:
        test_commands.append("npm test")
    if "build" in scripts:
        build_commands.append("npm run build")

    if "python" in languages:
        lint_commands.append("python3 -m ruff check .")
        test_commands.append("python3 -m pytest")

    if "go" in languages:
        lint_commands.append("go vet ./...")
        test_commands.append("go test ./...")
        build_commands.append("go build ./...")

    return StackReport(
        repo=str(repo.resolve()),
        languages=sorted(set(languages)),
        package_managers=sorted(set(package_managers)),
        ci_targets=ci_targets,
        test_commands=sorted(set(test_commands)),
        build_commands=sorted(set(build_commands)),
        lint_commands=sorted(set(lint_commands)),
        signals=signals,
    )


def format_text(report: StackReport) -> str:
    lines = [
        "Detected stack",
        f"- repo: {report.repo}",
        f"- languages: {', '.join(report.languages) if report.languages else 'none'}",
        f"- package managers: {', '.join(report.package_managers) if report.package_managers else 'none'}",
        f"- lint commands: {', '.join(report.lint_commands) if report.lint_commands else 'none'}",
        f"- test commands: {', '.join(report.test_commands) if report.test_commands else 'none'}",
        f"- build commands: {', '.join(report.build_commands) if report.build_commands else 'none'}",
    ]
    return "\n".join(lines)


def main() -> int:
    args = parse_args()
    payload = load_payload(args.input)

    if payload:
        try:
            report = StackReport(**payload)
        except TypeError as exc:
            raise CLIError(f"Invalid input payload for StackReport: {exc}") from exc
    else:
        repo = Path(args.repo).resolve()
        if not repo.exists() or not repo.is_dir():
            raise CLIError(f"Invalid repo path: {repo}")
        report = detect(repo)

    if args.format == "json":
        print(json.dumps(asdict(report), indent=2))
    else:
        print(format_text(report))

    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except CLIError as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        raise SystemExit(2)
