#!/usr/bin/env python3
import argparse
import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parent.parent
SCRIPT_INTERFACE = "cli"
SCRIPT_INTERFACE_REASON = "Turns redacted repeated preference patterns into proposal-only adaptation plans."

PROPOSAL_LIBRARY = {
    "language_default": {
        "title": "Keep reports Chinese-first with optional English",
        "change_type": "report-default-language",
        "risk_level": "low",
        "target_files": ["scripts/render_skill_overview.py", "references/artifact-design-doctrine.md"],
        "suggested_changes": [
            "Keep user-facing report copy Simplified Chinese by default.",
            "Expose English through the existing language switch instead of mixing languages in the default view.",
        ],
        "verification_commands": ["python3 tests/verify_skill_overview.py"],
        "rollback_plan": "Revert report language template changes and rerun the overview verifier.",
    },
    "report_ui": {
        "title": "Improve report layout, visual hierarchy, and chart readability",
        "change_type": "artifact-ui-polish",
        "risk_level": "medium",
        "target_files": ["scripts/render_skill_overview.py", "references/artifact-design-doctrine.md", "tests/verify_skill_overview.py"],
        "suggested_changes": [
            "Prefer vertical narrative sections with limited two-column layouts only when content has enough width.",
            "Keep charts inline SVG, with captions and stable responsive constraints.",
        ],
        "verification_commands": ["python3 tests/verify_skill_overview.py", "python3 tests/verify_skill_report_charts.py"],
        "rollback_plan": "Restore the previous report renderer and regenerate the demo report.",
    },
    "approval_safety": {
        "title": "Keep adaptive iteration approval-gated",
        "change_type": "privacy-governance",
        "risk_level": "low",
        "target_files": ["references/user-memory-policy.md", "references/autonomous-adaptation.md", "schemas/adaptation-proposal.schema.json"],
        "suggested_changes": [
            "Require explicit source paths for memory scans.",
            "Generate proposals before any source patching.",
            "Reserve automatic apply for a future approval ledger and rollback implementation.",
        ],
        "verification_commands": ["python3 tests/verify_adaptation_safety.py"],
        "rollback_plan": "Remove the adaptive proposal artifacts and keep feedback/adoption drift as the only iteration inputs.",
    },
    "delivery_format": {
        "title": "Make generated artifact paths explicit in CLI output",
        "change_type": "artifact-discoverability",
        "risk_level": "low",
        "target_files": ["scripts/yao.py", "README.md"],
        "suggested_changes": [
            "Include stable report paths in command output.",
            "Document which artifacts are meant for human review.",
        ],
        "verification_commands": ["python3 tests/verify_yao_cli.py"],
        "rollback_plan": "Revert CLI copy/documentation changes and keep artifact paths unchanged.",
    },
    "evidence_testing": {
        "title": "Attach tests and evidence refresh to each upgrade",
        "change_type": "quality-gate",
        "risk_level": "medium",
        "target_files": ["tests/verify_adaptation_safety.py", "scripts/render_skill_os2_coverage.py", "reports/skill_os2_coverage.json"],
        "suggested_changes": [
            "Add focused verifier coverage for every new adaptive behavior.",
            "Refresh Skill OS 2.0 coverage so planned, partial, and covered states remain visible.",
        ],
        "verification_commands": ["python3 tests/verify_adaptation_safety.py", "python3 tests/verify_skill_os2_coverage.py"],
        "rollback_plan": "Revert the new verifier and coverage status updates, then regenerate coverage reports.",
    },
}


def utc_now() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def display_path(path: Path, skill_dir: Path) -> str:
    try:
        return str(path.resolve().relative_to(skill_dir.resolve()))
    except ValueError:
        return str(path.resolve())


def resolve_output(skill_dir: Path, value: str) -> Path:
    path = Path(value)
    return path if path.is_absolute() else skill_dir / path


def load_json(path: Path) -> dict[str, Any]:
    if not path.exists():
        return {}
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        return {}
    return payload if isinstance(payload, dict) else {}


def proposal_id(pattern_id: str, support_count: int, target_files: list[str]) -> str:
    digest = hashlib.sha1(f"{pattern_id}:{support_count}:{','.join(target_files)}".encode("utf-8")).hexdigest()
    return f"adapt-{digest[:10]}"


def proposal_from_pattern(pattern: dict[str, Any]) -> dict[str, Any]:
    pattern_id = str(pattern.get("pattern_id") or "generic")
    spec = PROPOSAL_LIBRARY.get(
        pattern_id,
        {
            "title": "Review repeated preference pattern",
            "change_type": "manual-review",
            "risk_level": "medium",
            "target_files": ["README.md"],
            "suggested_changes": ["Review this repeated signal manually before changing skill behavior."],
            "verification_commands": ["make ci-test"],
            "rollback_plan": "Revert the approved patch and rerun the relevant verifier.",
        },
    )
    support_count = int(pattern.get("support_count") or 0)
    target_files = list(spec["target_files"])
    return {
        "proposal_id": proposal_id(pattern_id, support_count, target_files),
        "pattern_id": pattern_id,
        "title": spec["title"],
        "change_type": spec["change_type"],
        "status": "proposal-only",
        "requires_approval": True,
        "write_allowed_without_approval": False,
        "risk_level": spec["risk_level"],
        "reason": pattern.get("reason", "Repeated signal detected."),
        "support_count": support_count,
        "target_files": target_files,
        "suggested_changes": spec["suggested_changes"],
        "verification_commands": spec["verification_commands"],
        "rollback_plan": spec["rollback_plan"],
        "evidence_refs": [
            {
                "record_id": item.get("record_id", "unknown"),
                "excerpt": item.get("excerpt", ""),
            }
            for item in pattern.get("evidence", [])[:3]
            if isinstance(item, dict)
        ],
    }


def build_report(skill_dir: Path, patterns_json: Path, generated_at: str) -> dict[str, Any]:
    skill_dir = skill_dir.resolve()
    if not patterns_json.is_absolute():
        patterns_json = skill_dir / patterns_json
    patterns_payload = load_json(patterns_json)
    failures: list[str] = []
    if not patterns_payload:
        failures.append(f"Pattern report does not exist or is invalid: {display_path(patterns_json, skill_dir)}")
    elif patterns_payload.get("ok") is not True:
        failures.append("Pattern report is not ok; fix scan failures before proposal generation.")
    patterns = patterns_payload.get("patterns", []) if isinstance(patterns_payload.get("patterns"), list) else []
    proposals = [proposal_from_pattern(pattern) for pattern in patterns if isinstance(pattern, dict)] if not failures else []
    return {
        "schema_version": "1.0",
        "ok": not failures,
        "generated_at": generated_at,
        "skill_dir": display_path(skill_dir, skill_dir),
        "source_patterns": display_path(patterns_json, skill_dir),
        "summary": {
            "pattern_count": len(patterns),
            "proposal_count": len(proposals),
            "apply_supported": False,
            "failure_count": len(failures),
        },
        "proposal_contract": {
            "proposal_only": True,
            "approval_required": True,
            "writes_repository_files": False,
            "allowlisted_targets_required": True,
            "rollback_required_for_apply": True,
            "apply_command_available": False,
        },
        "proposals": proposals,
        "failures": failures,
        "artifacts": {
            "json": "reports/adaptation_proposals.json",
            "markdown": "reports/adaptation_proposals.md",
        },
    }


def render_markdown(report: dict[str, Any]) -> str:
    lines = [
        "# Adaptation Proposals",
        "",
        f"- Generated at: `{report['generated_at']}`",
        f"- Pattern report: `{report['source_patterns']}`",
        f"- Proposal only: `{str(report['proposal_contract']['proposal_only']).lower()}`",
        f"- Writes repository files: `{str(report['proposal_contract']['writes_repository_files']).lower()}`",
        f"- Proposals: `{report['summary']['proposal_count']}`",
        "",
    ]
    if not report["proposals"]:
        lines.append("No proposals were generated.")
    for proposal in report["proposals"]:
        lines.extend(
            [
                f"## {proposal['title']}",
                "",
                f"- ID: `{proposal['proposal_id']}`",
                f"- Status: `{proposal['status']}`",
                f"- Pattern: `{proposal['pattern_id']}`",
                f"- Risk: `{proposal['risk_level']}`",
                f"- Requires approval: `{str(proposal['requires_approval']).lower()}`",
                f"- Reason: {proposal['reason']}",
                "- Target files:",
            ]
        )
        lines.extend(f"  - `{path}`" for path in proposal["target_files"])
        lines.append("- Suggested changes:")
        lines.extend(f"  - {item}" for item in proposal["suggested_changes"])
        lines.append("- Verification:")
        lines.extend(f"  - `{item}`" for item in proposal["verification_commands"])
        lines.append(f"- Rollback: {proposal['rollback_plan']}")
        if proposal["evidence_refs"]:
            lines.append("- Redacted evidence refs:")
            lines.extend(f"  - `{item['record_id']}`: {item['excerpt']}" for item in proposal["evidence_refs"])
        lines.append("")
    if report["failures"]:
        lines.extend(["## Failures", ""])
        lines.extend(f"- {failure}" for failure in report["failures"])
    return "\n".join(lines).rstrip() + "\n"


def main() -> None:
    parser = argparse.ArgumentParser(description="Create proposal-only adaptation plans from summarized user signal patterns.")
    parser.add_argument("skill_dir", nargs="?", default=".")
    parser.add_argument("--patterns-json", default="reports/user_patterns.json")
    parser.add_argument("--output-json", default="reports/adaptation_proposals.json")
    parser.add_argument("--output-md", default="reports/adaptation_proposals.md")
    parser.add_argument("--generated-at", default=utc_now())
    args = parser.parse_args()

    skill_dir = Path(args.skill_dir).resolve()
    report = build_report(skill_dir, Path(args.patterns_json), args.generated_at)
    if report["ok"]:
        output_json = resolve_output(skill_dir, args.output_json)
        output_md = resolve_output(skill_dir, args.output_md)
        output_json.parent.mkdir(parents=True, exist_ok=True)
        output_md.parent.mkdir(parents=True, exist_ok=True)
        report["artifacts"] = {
            "json": display_path(output_json, skill_dir),
            "markdown": display_path(output_md, skill_dir),
        }
        output_json.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
        output_md.write_text(render_markdown(report), encoding="utf-8")
    print(json.dumps(report, ensure_ascii=False, indent=2))
    if not report["ok"]:
        raise SystemExit(2)


if __name__ == "__main__":
    main()
