"""Contract API: structured read/write for the visual editor."""

from __future__ import annotations

import logging
import re

from fastapi import APIRouter, Request
from pydantic import BaseModel

log = logging.getLogger(__name__)

router = APIRouter(prefix="/contract", tags=["contract"])


class ContractUpdate(BaseModel):
    """Structured contract update from the visual editor."""

    privacy_mode: str | None = None
    proactivity: str | None = None
    allow: list[str] | None = None
    confirm: list[str] | None = None
    block: list[str] | None = None
    overrides: dict[str, str] | None = None
    gated_dirs: list[str] | None = None
    journal_thinking: str | None = None
    journal_observations: str | None = None
    journal_decisions: str | None = None
    reflection_approval: str | None = None
    weekly_reflection: str | None = None
    coaching: str | None = None
    autopilot: str | None = None


@router.get("/structured")
async def get_structured_contract(request: Request) -> dict:
    """Get the contract as structured JSON for the visual editor."""
    contract = request.app.state.contract
    rules = contract.rules

    # Also read the raw contract for non-YAML settings
    vault = request.app.state.vault
    contract_path = vault.resolve_identity_path("CONTRACT.md")
    content = ""
    if vault.exists(contract_path):
        doc = vault.read(contract_path)
        content = doc.content

    # Discover all actions from all sources — nothing hidden
    all_actions = _discover_all_actions(rules)

    # Discover all vault directories grouped by category
    vault_dirs = _discover_vault_dirs(vault)
    # Include dirs from gated_dirs not yet on disk
    all_known = set(vault_dirs["user"] + vault_dirs["custom"])
    for d in rules.gated_dirs:
        if d not in all_known:
            vault_dirs["custom"].append(d)
    vault_dirs["custom"].sort()

    return {
        "permissions": {
            "default": rules.default,
            "allow": sorted(rules.allow),
            "confirm": sorted(rules.confirm),
            "block": sorted(rules.block),
            "overrides": rules.overrides,
            "gated_dirs": rules.gated_dirs,
        },
        "settings": {
            "privacy_mode": rules.privacy_mode,
            "proactivity": rules.proactivity,
            "coaching": rules.coaching,
            "autopilot": rules.autopilot,
            "journal_thinking": _extract_setting(content, "Thinking-out-loud detection", "ask"),
            "journal_observations": _extract_setting(content, "Agent observations", "on"),
            "journal_decisions": _extract_setting(content, "Agent decision logging", "on"),
            "reflection_approval": _extract_setting(content, "Reflection approval", "review"),
            "weekly_reflection": _extract_setting(content, "Weekly reflection", "on"),
        },
        "all_actions": all_actions,
        "vault_dirs": vault_dirs,
    }


@router.get("/tier-defaults/{tier}")
async def get_tier_defaults(tier: str) -> dict:
    """Return the permission preset for a proactivity tier.

    The UI calls this when the user changes the proactivity dropdown so the
    checkboxes snap to the new tier's defaults before the user saves.
    """
    from pathlib import Path

    import yaml

    if tier not in ("observer", "advisor", "assistant", "partner"):
        return {"error": f"Unknown tier: {tier}"}

    # Load the base defaults (advisor tier)
    default_contract = Path(__file__).resolve().parents[5] / "defaults" / "vault" / "CONTRACT.md"
    base_allow: set[str] = set()
    base_confirm: set[str] = set()
    base_block: set[str] = set()

    if default_contract.exists():
        content = default_contract.read_text(encoding="utf-8")
        m = re.search(r"```yaml\s*\n(.*?)```", content, flags=re.DOTALL)
        if m:
            rules = yaml.safe_load(m.group(1)) or {}
            base_allow = set(rules.get("allow", []) or [])
            base_confirm = set(rules.get("confirm", []) or [])
            base_block = set(rules.get("block", []) or [])

    # Load tier adjustments
    cfg_path = Path(__file__).resolve().parents[5] / "defaults" / "skills" / "_meta" / "onboarding" / "config" / "contract-tiers.yaml"
    tier_config: dict = {}
    if cfg_path.exists():
        try:
            tier_config = yaml.safe_load(cfg_path.read_text(encoding="utf-8")) or {}
        except Exception:
            pass

    adjustments = tier_config.get(tier, {})
    promote = set(adjustments.get("promote_to_allow", []) or [])
    demote = set(adjustments.get("demote_to_confirm", []) or [])

    # Apply adjustments
    allow = set(base_allow)
    confirm = set(base_confirm)

    for action in promote:
        if action in confirm:
            confirm.discard(action)
            allow.add(action)

    for action in demote:
        if action in allow:
            allow.discard(action)
            confirm.add(action)

    return {
        "tier": tier,
        "allow": sorted(allow),
        "confirm": sorted(confirm),
        "block": sorted(base_block),
    }


@router.put("/structured")
async def update_structured_contract(request: Request, body: ContractUpdate) -> dict:
    """Update the contract from the visual editor. Rewrites the YAML block."""
    contract = request.app.state.contract
    rules = contract.rules
    vault = request.app.state.vault
    writer = request.app.state.writer

    # Merge updates into current rules
    allow = set(body.allow) if body.allow is not None else rules.allow
    confirm = set(body.confirm) if body.confirm is not None else rules.confirm
    block = set(body.block) if body.block is not None else rules.block
    overrides = body.overrides if body.overrides is not None else rules.overrides
    gated_dirs = body.gated_dirs if body.gated_dirs is not None else rules.gated_dirs

    # Read current contract
    contract_path = vault.resolve_identity_path("CONTRACT.md")
    doc = vault.read(contract_path)
    content = doc.raw

    # Update privacy mode
    if body.privacy_mode:
        content = re.sub(
            r"(\*\*Mode\*\*:\s*)\w+", rf"\g<1>{body.privacy_mode}", content, count=1,
        )

    # Update proactivity — store in frontmatter (supports "custom").
    # The **Level**: prose line keeps the last named tier for readability.
    if body.proactivity:
        if re.search(r"(?m)^proactivity:", content):
            content = re.sub(r"(?m)^proactivity:\s*\S+", f"proactivity: {body.proactivity}", content)
        else:
            content = re.sub(r"(?m)^(---\s*\n)", rf"\g<1>proactivity: {body.proactivity}\n", content, count=1)
        # Only update the prose line for named tiers, not "custom"
        if body.proactivity != "custom":
            content = re.sub(
                r"(\*\*Level\*\*:\s*)\w+", rf"\g<1>{body.proactivity}", content, count=1,
            )

    # Update journal settings
    for field, label in [
        ("journal_thinking", "Thinking-out-loud detection"),
        ("journal_observations", "Agent observations"),
        ("journal_decisions", "Agent decision logging"),
        ("reflection_approval", "Reflection approval"),
        ("weekly_reflection", "Weekly reflection"),
    ]:
        val = getattr(body, field, None) if hasattr(body, field) else None
        if val:
            content = re.sub(
                rf"(\*\*{re.escape(label)}\*\*:\s*)\w+",
                rf"\g<1>{val}", content, count=1,
            )

    # Update coaching
    if body.coaching:
        content = re.sub(
            r"(\*\*Coaching\*\*:\s*)\w+", rf"\g<1>{body.coaching}", content, count=1,
        )

    # Update autopilot
    if body.autopilot:
        content = re.sub(
            r"(\*\*Mode\*\*:\s*)(off|on|scheduled)",
            rf"\g<1>{body.autopilot}", content,
        )

    # Rebuild YAML block
    yaml_lines = ["default: deny", ""]
    yaml_lines.append("allow:")
    for a in sorted(allow):
        yaml_lines.append(f"  - {a}")
    yaml_lines.append("")
    yaml_lines.append("confirm:")
    for a in sorted(confirm):
        yaml_lines.append(f"  - {a}")
    yaml_lines.append("")
    yaml_lines.append("block:")
    for a in sorted(block):
        yaml_lines.append(f"  - {a}")
    yaml_lines.append("")
    yaml_lines.append("overrides:")
    if overrides:
        for k, v in overrides.items():
            yaml_lines.append(f"  {k}: {v}")
    else:
        yaml_lines.append("  # No overrides configured")
    yaml_lines.append("")
    yaml_lines.append("gated_dirs:")
    for d in gated_dirs:
        yaml_lines.append(f"  - {d}")

    new_yaml = "\n".join(yaml_lines)
    yaml_block = f"```yaml\n{new_yaml}\n```"

    # Replace the YAML block in the contract, or insert one if missing
    if re.search(r"```yaml\s*\n.*?```", content, flags=re.DOTALL):
        content = re.sub(
            r"```yaml\s*\n.*?```",
            yaml_block,
            content,
            flags=re.DOTALL,
        )
    else:
        # No YAML block exists — append after frontmatter or at end
        # Look for end of YAML frontmatter (second ---)
        fm_match = re.search(r"^---\n.*?\n---\n", content, flags=re.DOTALL)
        if fm_match:
            insert_at = fm_match.end()
            content = content[:insert_at] + "\n" + yaml_block + "\n" + content[insert_at:]
        else:
            content = content.rstrip() + "\n\n" + yaml_block + "\n"

    # Write back (always to identity/ canonical location)
    writer.write("system/identity/CONTRACT.md", content, trust_reason="contract_editor")

    # Force reload
    contract.reload()

    return {"status": "updated"}


def _extract_setting(content: str, label: str, default: str) -> str:
    """Extract a setting value from markdown like **Label**: value."""
    match = re.search(rf"\*\*{re.escape(label)}\*\*:\s*(\w+)", content)
    return match.group(1) if match else default


# All known actions for the UI
ALL_ACTIONS = {
    # Reading & Search
    "read_inbox": "Read email inbox and messages",
    "read_vault": "Read vault files and documents",
    "search": "Search knowledge base and wiki articles",
    "vault_search": "Full-text search across all vault content",
    "list_tasks": "List tasks and their status",
    "list_goals": "List goals and their progress",
    "list_folders": "List email folders and labels",
    # Creating & Writing
    "draft_content": "Draft emails and documents (saved, not sent)",
    "journal_write": "Write entries to the user's journal",
    "log_observation": "Log agent observations about the day",
    "log_decision": "Record decisions in the journal",
    "create_task": "Create new tasks and delegate work",
    "modify_project": "Create, update, and add milestones to projects",
    "update_memory": "Update long-term memory from reflections",
    "write_vault": "Correct facts in memory and write vault files",
    # Sending & Acting
    "send_email": "Send email on your behalf",
    "send_message": "Send messages via WhatsApp, Telegram, Slack, Discord",
    "send_notification": "Push notifications and alerts",
    "move_email": "Move emails between folders",
    "delete_email": "Delete emails permanently",
    "manage_channels": "Toggle channel modes (e.g. WhatsApp answering mode)",
    # Delegation & Orchestration
    "delegate_task": "Dispatch tasks to subagents",
    # Canvas
    "canvas_push": "Push, update, remove, snapshot, or reset the live canvas",
    # Automation
    "heartbeat": "Background connectivity monitoring (15-min pulse)",
    "daily_briefing": "Morning briefing and end-of-day reports",
    "audit_log": "Write to the security audit trail",
    # Calendar & Events
    "create_event": "Create calendar events",
    "schedule_calendar": "Manage and modify calendar schedule",
    # Knowledge Base
    "reset_wiki": "Delete all wiki articles in a knowledge base (keeps sources)",
    # Sensitive & Restricted
    "modify_vault_file": "Modify existing vault files in-place",
    "delete_vault_file": "Permanently delete vault files",
    "financial_entry": "Make financial ledger entries",
    "access_financial_account": "Access financial accounts and balances",
    "send_gated_content": "Send gated-directory content to cloud LLM",
    "share_external": "Share vault data with external services",
    "override_security": "Override Guardian security boundaries",
    "modify_contract": "Modify this contract (self-modification)",
    "modify_soul": "Modify the agent's core identity (SOUL.md)",
    "log_vault_content": "Include vault content in application logs",
    "store_content_in_heartbeat": "Store user content in heartbeat state",
}


def _discover_all_actions(rules) -> dict[str, str]:
    """Merge all action sources so the UI shows everything — nothing hidden."""
    merged = dict(ALL_ACTIONS)

    try:
        from agntspace.agent.tools import TOOL_CONTRACT_MAP
        for action in set(TOOL_CONTRACT_MAP.values()):
            if action not in merged:
                merged[action] = "(from tool registry — no description yet)"
                log.warning("Contract action '%s' in TOOL_CONTRACT_MAP but not in ALL_ACTIONS", action)
    except ImportError:
        pass

    try:
        from agntspace.api.contract_middleware import ROUTE_CONTRACT_MAP
        for _, _, action in ROUTE_CONTRACT_MAP:
            if action not in merged:
                merged[action] = "(from API routes — no description yet)"
                log.warning("Contract action '%s' in ROUTE_CONTRACT_MAP but not in ALL_ACTIONS", action)
    except ImportError:
        pass

    for action in (rules.allow | rules.confirm | rules.block):
        if action not in merged:
            merged[action] = "(custom action — added by user)"

    return merged


def _discover_vault_dirs(vault) -> dict[str, list[str]]:
    """Return gatable vault directories grouped by category for the contract UI."""
    # Internal dirs (system/, memory/) are agent-managed, not user-facing
    _INTERNAL_NAMES = {"system", "memory"}

    vault_dir = vault.vault_dir
    user_dirs = []
    custom_dirs = []

    for p in vault_dir.iterdir():
        if not p.is_dir() or p.name.startswith("."):
            continue
        name = p.name
        if name in _INTERNAL_NAMES:
            continue
        else:
            custom_dirs.append(name)

    return {
        "user": sorted(user_dirs),
        "custom": sorted(custom_dirs),
    }
