"""scaffold task: create file(s) from templates using contract ## Scaffold section."""

from __future__ import annotations

import difflib
import pathlib

from milco.core.run_context import RunContext
from milco.core.artifacts import write_artifact, ensure_all_artifacts_exist
from milco.core.contract import parse_scaffold_config
from milco.tasks.base import FixResult
from milco.tasks.registry import register_task, TaskBase

# Bundled template names (files in milco/templates/<name>.txt)
SCAFFOLD_TEMPLATES = ("python_module", "pytest_file", "package_init", "script")


def _templates_dir() -> pathlib.Path:
    import milco

    return pathlib.Path(milco.__file__).parent / "templates"


def _load_template(name: str) -> str:
    path = _templates_dir() / f"{name}.txt"
    if not path.exists():
        raise FileNotFoundError(f"Template '{name}' not found at {path}")
    return path.read_text(encoding="utf-8")


def _make_unified_diff(old_content: str, new_content: str, filename: str) -> str:
    old_lines = old_content.splitlines(keepends=True) if old_content else []
    new_lines = new_content.splitlines(keepends=True)
    diff = difflib.unified_diff(
        old_lines,
        new_lines,
        fromfile=f"a/{filename}",
        tofile=f"b/{filename}",
    )
    return "".join(diff)


def _default_vars(template: str, config: dict[str, str]) -> dict[str, str]:
    """Build substitution dict with required defaults."""
    vars_: dict[str, str] = {
        "module_name": config.get("module_name", ""),
        "docstring": config.get("docstring", ""),
    }
    if template == "package_init":
        raw = config.get("exports", "").strip()
        if raw:
            parts = [f'"{p.strip()}"' for p in raw.split(",") if p.strip()]
            vars_["all_exports"] = "[" + ", ".join(parts) + "]"
        else:
            vars_["all_exports"] = "[]"
    if template == "script":
        vars_["description"] = (
            config.get("description", "")
            or vars_.get("docstring", "")
            or f"CLI for {vars_.get('module_name', 'script')}"
        )
    for k, v in config.items():
        if k not in ("template", "target"):
            vars_[k] = v
    return vars_


@register_task("scaffold")
class ScaffoldTask(TaskBase):
    needs_llm = False
    description = "Create file(s) from templates (contract ## Scaffold section)"

    def fix(self, ctx: RunContext) -> FixResult:
        ctx.ensure_run_dir()
        errors: list[str] = []

        from milco.policy.policy import check_evidence_first

        evidence_path = ctx.artifact_path("evidence.md")
        evidence_text = (
            evidence_path.read_text(encoding="utf-8") if evidence_path.exists() else ""
        )
        ok, msg = check_evidence_first(evidence_text)
        if not ok:
            errors.append(msg)
            ensure_all_artifacts_exist(ctx)
            return FixResult(success=False, applied=False, diff_text="", errors=errors)

        contract_file = ctx.artifact_path("task_contract.md")
        if contract_file.exists():
            contract_text = contract_file.read_text(encoding="utf-8")
        else:
            fallback = ctx.repo_root / (ctx.contract_path or "TASK_CONTRACT.md")
            if not fallback.exists():
                errors.append(
                    "No contract found (task_contract.md or TASK_CONTRACT.md). Run audit first."
                )
                ensure_all_artifacts_exist(ctx)
                return FixResult(
                    success=False, applied=False, diff_text="", errors=errors
                )
            contract_text = fallback.read_text(encoding="utf-8")
        config = parse_scaffold_config(contract_text)
        if not config:
            errors.append("Contract has no ## Scaffold section.")
            ensure_all_artifacts_exist(ctx)
            return FixResult(success=False, applied=False, diff_text="", errors=errors)

        template_name = (config.get("template") or "").strip()
        target = (config.get("target") or "").strip()
        if not template_name:
            errors.append("Scaffold section missing 'template:'.")
            ensure_all_artifacts_exist(ctx)
            return FixResult(success=False, applied=False, diff_text="", errors=errors)
        if not target:
            errors.append("Scaffold section missing 'target:' path.")
            ensure_all_artifacts_exist(ctx)
            return FixResult(success=False, applied=False, diff_text="", errors=errors)

        if template_name not in SCAFFOLD_TEMPLATES:
            errors.append(
                f"Unknown template '{template_name}'. Available: {', '.join(SCAFFOLD_TEMPLATES)}"
            )
            ensure_all_artifacts_exist(ctx)
            return FixResult(success=False, applied=False, diff_text="", errors=errors)

        try:
            raw = _load_template(template_name)
        except FileNotFoundError as e:
            errors.append(str(e))
            ensure_all_artifacts_exist(ctx)
            return FixResult(success=False, applied=False, diff_text="", errors=errors)

        vars_ = _default_vars(template_name, config)
        if not vars_.get("module_name") and "module_name" in raw:
            errors.append(
                "Scaffold section should set 'module_name:' for this template."
            )
            ensure_all_artifacts_exist(ctx)
            return FixResult(success=False, applied=False, diff_text="", errors=errors)

        try:
            new_content = raw.format(**vars_)
        except KeyError as e:
            errors.append(f"Template placeholder missing in contract: {e}")
            ensure_all_artifacts_exist(ctx)
            return FixResult(success=False, applied=False, diff_text="", errors=errors)

        target_path = ctx.repo_root / target
        old_content = (
            target_path.read_text(encoding="utf-8") if target_path.exists() else ""
        )
        diff_text = _make_unified_diff(old_content, new_content, target)

        if not diff_text.strip():
            diff_text = "# No changes needed – target file is up to date.\n"

        write_artifact(ctx, "patches.diff", diff_text)

        applied = False
        if ctx.can_apply():
            target_path.parent.mkdir(parents=True, exist_ok=True)
            target_path.write_text(new_content, encoding="utf-8")
            applied = True

        ensure_all_artifacts_exist(ctx)
        return FixResult(
            success=len(errors) == 0,
            applied=applied,
            diff_text=diff_text,
            errors=errors,
        )
