"""Runbook generator — turn a finding into a copy-pasteable Ansible playbook
or Python+netmiko script. Inspired by NetBrain's runbook conversion.
"""
from __future__ import annotations

from textwrap import indent


def _safe(name: str) -> str:
    return "".join(c if (c.isalnum() or c in "-_") else "_" for c in name)


def to_ansible_playbook(
    finding: dict,
    hostnames: list[str],
    platform_hint: str = "junos",
) -> str:
    """Generate an Ansible playbook that applies the finding's patches."""
    title = finding.get("title", "remediation")
    severity = finding.get("severity", "medium")
    rationale = finding.get("rationale") or finding.get("root_cause") or ""

    # Pull a flat list of CLI commands from the finding
    cmds = _extract_cmds(finding, platform_hint)
    if not cmds:
        cmds = ["# No platform-specific commands found in the finding"]

    hosts_block = ", ".join(hostnames) or "all"
    if platform_hint.lower() == "frr":
        task_body = "      ansible.builtin.shell: |\n" + indent("\n".join(cmds), "        ")
    elif platform_hint.lower() == "eos":
        task_body = "      arista.eos.eos_config:\n        lines:\n" + indent(
            "\n".join(f"- '{c}'" for c in cmds), "          "
        )
    else:  # junos
        task_body = "      junipernetworks.junos.junos_config:\n        lines:\n" + indent(
            "\n".join(f"- '{c}'" for c in cmds), "          "
        )

    return f"""---
# Auto-generated by AI Log Analyzer
# Finding:  [{severity.upper()}] {title}
# Why:      {rationale[:140].replace(chr(10), ' ')}
# Targets:  {hosts_block}
- name: "Remediate: {title[:60]}"
  hosts: {hosts_block}
  gather_facts: false
  serial: 1
  tasks:
    - name: "Apply patch ({platform_hint})"
{task_body}
      register: result

    - name: "Show diff"
      ansible.builtin.debug:
        var: result
"""


def to_netmiko_script(
    finding: dict,
    hostnames: list[str],
    platform_hint: str = "junos",
) -> str:
    """Generate a Python + netmiko script."""
    title = finding.get("title", "remediation")
    severity = finding.get("severity", "medium")
    cmds = _extract_cmds(finding, platform_hint)
    if not cmds:
        cmds = ["# No platform-specific commands found"]

    device_type = {"junos": "juniper_junos", "eos": "arista_eos",
                   "frr": "linux"}.get(platform_hint.lower(), "linux")

    return f"""#!/usr/bin/env python3
\"\"\"Auto-generated by AI Log Analyzer.

Finding: [{severity.upper()}] {title}
Targets: {', '.join(hostnames) or '<edit DEVICES list>'}
\"\"\"
import os
import sys
from netmiko import ConnectHandler

DEVICES = {hostnames or ['device-name-here']!r}
USERNAME = os.environ.get("NET_USER") or "admin"
PASSWORD = os.environ.get("NET_PASS")  # never hardcode

COMMANDS = [
{indent(chr(10).join(repr(c) + ',' for c in cmds), '    ')}
]


def remediate(host: str) -> None:
    conn = ConnectHandler(
        device_type={device_type!r},
        host=host,
        username=USERNAME,
        password=PASSWORD,
        timeout=30,
    )
    print(f"[{{host}}] applying {{len(COMMANDS)}} command(s)…")
    output = conn.send_config_set(COMMANDS)
    print(output)
    # Junos / Arista commits handled by send_config_set in most netmiko versions;
    # add explicit commit if your driver needs it.
    conn.disconnect()


def main() -> None:
    for host in DEVICES:
        try:
            remediate(host)
        except Exception as e:
            print(f"[{{host}}] ERROR: {{e}}", file=sys.stderr)


if __name__ == "__main__":
    main()
"""


def _extract_cmds(finding: dict, platform_hint: str) -> list[str]:
    """Extract platform-specific CLI commands from a finding's deep_analysis or fix_per_device."""
    plat = platform_hint.lower()
    out: list[str] = []

    # Site-level finding shape: fix_per_device: {hostname: [cmds]}
    fpd = finding.get("fix_per_device") or {}
    if fpd:
        for cmds in fpd.values():
            if isinstance(cmds, list):
                out.extend(cmds)

    # Single-device finding shape: patch: [cmds]
    patch = finding.get("patch") or []
    if isinstance(patch, list):
        out.extend(patch)

    # Phased deep_analysis (action items)
    deep = finding.get("deep_analysis") or {}
    if deep:
        for phase in deep.get("phases") or []:
            for action in phase.get("actions") or []:
                cli = action.get("cli") or {}
                if isinstance(cli, dict):
                    if plat in cli:
                        out.append(cli[plat])
                    elif "any" in cli:
                        out.append(cli["any"])

    # De-dup while preserving order
    seen: set[str] = set()
    uniq: list[str] = []
    for c in out:
        c = (c or "").strip()
        if c and c not in seen:
            seen.add(c)
            uniq.append(c)
    return uniq
