"""Skill exchange logic for the ALP network.

Handles pulling skills from peers, running security scans, adapting
foreign code to the local environment via LLM, testing in sandbox,
and saving with lineage tracking.
"""

from __future__ import annotations

import asyncio
import json
import re
from datetime import datetime, timezone
from typing import Any

import httpx

from alive.network.conversation_log import log_event
from alive.network.security import scan_for_risks


async def pull_skill(
    peer_ip: str,
    peer_port: int,
    skill_name: str,
    *,
    auto_approve: bool = False,
) -> dict[str, Any] | None:
    """Pull a skill from a peer, adapt it, test it, save it.

    Args:
        peer_ip: Peer's IP address.
        peer_port: Peer's HTTP port.
        skill_name: Name of the skill to pull.
        auto_approve: If True, skip interactive approval (for testing).

    Returns:
        Metadata dict of installed skill, or None on failure.
    """
    from rich.console import Console
    from rich.panel import Panel
    from rich.syntax import Syntax

    console = Console()

    # 1. Fetch the skill package
    url = f"http://{peer_ip}:{peer_port}/alp/skill/{skill_name}"
    log_event("PULLING", f"{skill_name} from {peer_ip}:{peer_port}")

    try:
        resp = httpx.get(url, timeout=30)
        resp.raise_for_status()
        foreign_skill = resp.json()
    except Exception as e:
        console.print(f"[red]Failed to fetch skill: {e}[/red]")
        return None

    code = foreign_skill.get("code") or ""
    generation = foreign_skill.get("generation", 1)
    lineage = foreign_skill.get("lineage", [])

    # 2. Security scan
    risks = scan_for_risks(code)

    # 3. Show preview
    lineage_display = " \u2192 ".join(lineage) if lineage else "origin"
    console.print(
        Panel(
            f"[bold]{skill_name}[/bold] (generation {generation})\n"
            f"Lineage: {lineage_display} \u2192 [bold green]YOU[/bold green]\n"
            f"Confidence: {foreign_skill.get('metadata', {}).get('confidence_score', 0):.0%}",
            title="\U0001f4e6 Skill Preview",
            border_style="cyan",
        )
    )

    if code:
        syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
        console.print(syntax)

    if risks:
        console.print()
        for risk in risks:
            console.print(f"  {risk}")
        console.print()
        default_answer = "n"
    else:
        console.print("\n  \u2705 Security scan: [green]clean[/green]\n")
        default_answer = "y"

    # 4. Ask for approval
    if not auto_approve:
        prompt = (
            f"Install this skill? [Y/n]: "
            if default_answer == "y"
            else f"Install this skill? [y/N]: "
        )
        answer = input(prompt).strip().lower() or default_answer
        if answer not in ("y", "yes"):
            console.print("[yellow]Skipped.[/yellow]")
            return None

    # 5. Adapt to local environment
    console.print("\n[dim]Adapting to your environment...[/dim]")
    log_event("MUTATING", f"Adapting {skill_name} to local env...")

    adapted_code = await adapt_skill(code)
    mutations = describe_mutations(code, adapted_code)

    for m in mutations:
        console.print(f"  \u2713 {m}")

    log_event("MUTATED", ", ".join(mutations))

    # 6. Test in sandbox
    console.print("\n[dim]Testing in sandbox...[/dim]")
    log_event("TESTING", f"{skill_name} in sandbox")

    from alive.sandbox.runner import run_skill_code

    test_result = run_skill_code(adapted_code, input_data={})

    if not test_result.success:
        # Fallback to original code
        test_result = run_skill_code(code, input_data={})
        if not test_result.success:
            console.print("[red]\u2717 Skill failed testing. Not installed.[/red]")
            log_event("TEST_FAILED", f"{skill_name} failed sandbox test")
            return None
        adapted_code = code
        mutations = ["unchanged (original passed)"]

    console.print("  \u2713 Tests passed")
    log_event("TESTED", f"{skill_name} passed sandbox test")

    # 7. Save with lineage
    from alive.core.skill_registry import get_registry
    from alive.network.identity import get_or_create_identity

    identity = get_or_create_identity()
    metadata = foreign_skill.get("metadata", {})
    if isinstance(metadata, str):
        try:
            metadata = json.loads(metadata)
        except (json.JSONDecodeError, TypeError):
            metadata = {}
    metadata = dict(metadata)

    metadata["origin_peer"] = metadata.get(
        "origin_peer", f"{peer_ip}:{peer_port}"
    )
    metadata["generation"] = generation + 1
    metadata["lineage"] = lineage + [identity["agent_id"]]
    metadata["mutations"] = mutations
    metadata["received_from"] = f"{peer_ip}:{peer_port}"
    metadata["received_at"] = datetime.now(timezone.utc).isoformat()
    metadata["network_evolved"] = True

    registry = get_registry()
    description = foreign_skill.get("description", "")
    deps = metadata.get("dependencies", [])

    registry.register(
        name=skill_name,
        code=adapted_code,
        description=description,
        dependencies=deps if isinstance(deps, list) else [],
        test_code=foreign_skill.get("test_code"),
        skill_doc=description,
    )

    # Overwrite metadata with network fields
    meta_path = registry._skills_dir / skill_name / "metadata.json"
    if meta_path.exists():
        try:
            saved_meta = json.loads(meta_path.read_text())
            saved_meta.update(metadata)
            meta_path.write_text(json.dumps(saved_meta, indent=2))
        except (json.JSONDecodeError, OSError):
            pass

    new_gen = metadata["generation"]
    new_lineage = " \u2192 ".join(metadata["lineage"][-4:])
    console.print(
        Panel(
            f"[bold green]{skill_name}[/bold green] (generation {new_gen})\n"
            f"Mutations: {', '.join(mutations)}\n"
            f"Lineage: {new_lineage}",
            title="\u2705 Skill Installed",
            border_style="green",
        )
    )
    log_event(
        "INSTALLED",
        f"{skill_name} (gen {new_gen}) from {peer_ip}:{peer_port}",
    )

    return metadata


async def adapt_skill(foreign_code: str) -> str:
    """Use LLM to adapt a foreign skill to the local environment.

    Args:
        foreign_code: The original Python source code.

    Returns:
        Adapted Python source code.
    """
    try:
        from alive.integrations import get_available_integrations
        from alive.core.skill_registry import get_registry
        from alive.llm import get_provider

        llm = get_provider()
        local_integrations = get_available_integrations()
        local_skills = get_registry().list_skills_only()

        integration_names = [
            k for k, v in local_integrations.items() if v.get("configured")
        ]
        skill_names = [s.name for s in local_skills]

        prompt = (
            "You received this skill from another AI agent on the ALP network:\n\n"
            f"```python\n{foreign_code}\n```\n\n"
            f"Your environment has these integrations configured: {integration_names}\n"
            f"Your existing skills: {skill_names}\n\n"
            "Adapt this skill to work optimally in YOUR environment:\n"
            "1. If it uses integrations you have (Telegram, webhook, browser), wire them in\n"
            "2. Replace any hardcoded values specific to the original user\n"
            "3. If you have complementary skills, add composition hooks\n"
            "4. Keep the core logic intact — don't break what works\n"
            "5. Maintain the run(input_data: dict) -> dict interface\n\n"
            "Return ONLY the adapted Python code. No explanation."
        )

        adapted = await llm.complete(
            system_prompt=(
                "You are Alive's skill adaptation engine. You adapt foreign "
                "skills to the local environment while preserving core functionality."
            ),
            user_prompt=prompt,
        )

        # Strip markdown code fences
        adapted = adapted.strip()
        if adapted.startswith("```python"):
            adapted = adapted[9:]
        if adapted.startswith("```"):
            adapted = adapted[3:]
        if adapted.endswith("```"):
            adapted = adapted[:-3]

        return adapted.strip()
    except Exception:
        # If LLM not available, return original code unchanged
        return foreign_code


def describe_mutations(original_code: str, adapted_code: str) -> list[str]:
    """Describe what changed between original and adapted skill.

    Args:
        original_code: The foreign skill's code.
        adapted_code: The adapted code.

    Returns:
        List of human-readable mutation descriptions.
    """
    mutations: list[str] = []

    if "send_telegram" in adapted_code and "send_telegram" not in original_code:
        mutations.append("+telegram alerts")
    if "send_webhook" in adapted_code and "send_webhook" not in original_code:
        mutations.append("+webhook notifications")
    if "BrowserTool" in adapted_code and "BrowserTool" not in original_code:
        mutations.append("+browser automation")
    if "search_web" in adapted_code and "search_web" not in original_code:
        mutations.append("+web search")
    if "SkillMemory" in adapted_code and "SkillMemory" not in original_code:
        mutations.append("+persistent memory")

    old_tz = set(re.findall(r"timezone|tz|UTC|IST|EST|PST", original_code))
    new_tz = set(re.findall(r"timezone|tz|UTC|IST|EST|PST", adapted_code))
    if old_tz != new_tz:
        mutations.append("+local timezone")

    if not mutations:
        mutations.append("minor adaptations")

    return mutations
