"""Protocol generator core logic."""

import json
import shutil
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, cast

from cokodo_agent.config import AI_TOOLS, TECH_STACKS


def generate_protocol(
    source_path: Path,
    target_path: Path,
    config: Dict[str, Any],
    force: bool = False,
) -> list[str]:
    """
    Generate .agent protocol in target directory.

    Args:
        source_path: Path to source protocol files
        target_path: Target project directory
        config: Configuration dictionary from prompts
        force: Overwrite existing .agent if True

    Returns:
        List of generated file/directory paths relative to target_path.
    """

    agent_dir = target_path / ".agent"

    # Remove existing if force
    if agent_dir.exists():
        if force:
            shutil.rmtree(agent_dir)
        else:
            raise FileExistsError(f".agent already exists at {agent_dir}")

    # Copy protocol files
    shutil.copytree(source_path, agent_dir)

    # Ensure project/ directory exists (GitHub Release package omits it)
    project_dir = agent_dir / "project"
    project_dir.mkdir(parents=True, exist_ok=True)

    # Populate project/ with template files for any files not already present.
    # The GitHub Release package ships without project/ (only templates/project/
    # contains the canonical stubs), so we seed missing files from the bundled
    # templates/ directory as a fallback.
    bundled_templates = Path(__file__).parent / "bundled" / "agent" / "templates" / "project"
    if bundled_templates.is_dir():
        for tmpl in bundled_templates.iterdir():
            if tmpl.is_file() and not (project_dir / tmpl.name).exists():
                shutil.copy2(tmpl, project_dir / tmpl.name)

    # Ensure project/sop/ directory exists and is seeded with bundled SOP templates
    sop_dir = project_dir / "sop"
    sop_dir.mkdir(parents=True, exist_ok=True)
    bundled_sop_templates = Path(__file__).parent / "bundled" / "agent" / "templates" / "project" / "sop"
    if bundled_sop_templates.is_dir():
        for tmpl in bundled_sop_templates.iterdir():
            if tmpl.is_file() and not (sop_dir / tmpl.name).exists():
                shutil.copy2(tmpl, sop_dir / tmpl.name)

    # Customize project files (context, tech-stack, status are generated fresh)
    _customize_context(agent_dir, config)
    _customize_tech_stack(agent_dir, config)
    _customize_status(agent_dir, config)

    # Generate AI tool adapters and collect results
    generated = _generate_adapters(target_path, config)
    return [".agent/"] + generated


def _customize_context(agent_dir: Path, config: Dict[str, Any]) -> None:
    """Customize project/context.md with user input."""

    context_file = agent_dir / "project" / "context.md"

    content = f"""# Project Context

## Project Name

{config.get('project_name', 'Unnamed Project')}

## Description

{config.get('description', '[TODO: Add project description]')}

## Current Status

[TODO: Describe current development status]

## Key Features

[TODO: List main features]

## Business Rules

[TODO: Document important business logic]

---

*Generated by cokodo-agent on {datetime.now().strftime('%Y-%m-%d')}*
"""

    context_file.write_text(content, encoding="utf-8")


def _customize_tech_stack(agent_dir: Path, config: Dict[str, Any]) -> None:
    """Customize project/tech-stack.md with user input."""

    tech_stack_file = agent_dir / "project" / "tech-stack.md"

    stack_key = config.get("tech_stack", "other")
    stack_name = TECH_STACKS.get(stack_key, "Other")

    # Stack-specific recommendations
    stack_recommendations = {
        "python": """
## Recommended Tools

- Package Manager: uv / pip
- Linting: ruff
- Testing: pytest
- Type Checking: mypy
""",
        "rust": """
## Recommended Tools

- Package Manager: cargo
- Linting: clippy
- Testing: cargo test
- Formatting: rustfmt
""",
        "qt": """
## Recommended Tools

- Build System: CMake / qmake
- IDE: Qt Creator
- Testing: Qt Test
""",
        "mixed": """
## Recommended Tools

### Python
- Package Manager: uv / pip
- Linting: ruff

### Rust
- Package Manager: cargo
- Linting: clippy
""",
    }

    recommendations = stack_recommendations.get(
        stack_key,
        """
## Recommended Tools

[TODO: Add your tooling recommendations]
""",
    )

    content = f"""# Tech Stack

## Primary Stack

{stack_name}

## Language Versions

[TODO: Specify versions, e.g., Python 3.11+, Rust 1.70+]

## Key Dependencies

[TODO: List main dependencies]

{recommendations}

## Development Environment

[TODO: Describe development setup]

---

*Generated by cokodo-agent on {datetime.now().strftime('%Y-%m-%d')}*
"""

    tech_stack_file.write_text(content, encoding="utf-8")


def _customize_status(agent_dir: Path, config: Dict[str, Any]) -> None:
    """Create project/status.md with initial project state."""

    status_file = agent_dir / "project" / "status.md"

    project_name = config.get("project_name", "Project")
    today = datetime.now().strftime("%Y-%m-%d")

    content = f"""# Project Status

> Last updated: {today} | Updated by: co init

---

## Current Phase

Project initialized. Ready for development.

## Active Goals

| # | Goal | Priority | Status |
|---|------|----------|--------|
| 1 | Set up project foundation | High | In Progress |

## Task Board

### In Progress
- [ ] Fill in project/context.md with {project_name} information
- [ ] Fill in project/tech-stack.md with technology decisions

### Pending
- [ ] Define development goals and roadmap
- [ ] Set up development environment

### Recently Completed
- [x] Initialize .agent protocol

## Blockers

None.

## Session Context

- Project just initialized with cokodo-agent on {today}.
- Next step: fill in project context and tech stack files, then define goals.

## Roadmap Overview

| Phase | Focus | Status |
|-------|-------|--------|
| Setup | Project initialization and configuration | Current |

## Quick Reference

| Item | Value |
|------|-------|
| Protocol | v3.2.0 |
| Status | Initialized |

---

*This file is the primary entry point for AI session continuity.*
*Update after each significant session: refresh tasks, goals, blockers, and session context.*
"""

    status_file.write_text(content, encoding="utf-8")


def _project_name_from_context(agent_dir: Path) -> str:
    """Read project name from .agent/project/context.md if present."""
    context_file = agent_dir / "project" / "context.md"
    if not context_file.exists():
        return "Project"
    text = context_file.read_text(encoding="utf-8")
    in_name_section = False
    for line in text.splitlines():
        stripped = line.strip()
        if "## " in stripped and "name" in stripped.lower():
            in_name_section = True
            continue
        if in_name_section:
            if stripped.startswith(("#", "|", "-", ">", "---", "[")):
                break
            if stripped:
                return stripped.replace("{{PROJECT_NAME}}", "Project").strip() or "Project"
    return "Project"


def generate_adapters_for_tools(
    target_path: Path,
    agent_dir: Path,
    tool_keys: list[str],
) -> None:
    """
    Generate IDE adapter entry files for existing .agent directory.

    Each adapter is a thin pointer that instructs the IDE to load
    ``.agent/start-here.md`` as the protocol entry point.  The actual
    protocol content lives exclusively inside ``.agent/``; adapters
    never duplicate it.

    Args:
        target_path: Project root (parent of .agent)
        agent_dir: Path to .agent directory
        tool_keys: One or more of: cursor, claude, copilot, gemini
    """
    project_name = _project_name_from_context(agent_dir)

    _GENERATORS: dict[str, Any] = {
        "cursor": _generate_cursor_adapter,
        "claude": _generate_claude_adapter,
        "copilot": _generate_copilot_adapter,
        "gemini": _generate_gemini_adapter,
    }

    for tool_key in tool_keys:
        if tool_key not in AI_TOOLS:
            continue
        tool_info = AI_TOOLS[tool_key]
        if tool_info["file"] is None:
            continue
        gen_fn = _GENERATORS.get(tool_key)
        if gen_fn is None:
            continue
        gen_fn(target_path, project_name)

    generate_mcp_configs(target_path, tool_keys)


def _generate_adapters(target_path: Path, config: Dict[str, Any]) -> list[str]:
    """Generate AI tool adapter files (called from ``generate_protocol``).

    Returns:
        List of generated file/directory paths relative to target_path.
    """

    ai_tools = config.get("ai_tools", [])
    if not ai_tools:
        return []

    project_name = config.get("project_name", "Project")

    _GENERATORS: dict[str, Any] = {
        "cursor": _generate_cursor_adapter,
        "claude": _generate_claude_adapter,
        "copilot": _generate_copilot_adapter,
        "gemini": _generate_gemini_adapter,
    }

    generated: list[str] = []
    for tool_key in ai_tools:
        if tool_key not in AI_TOOLS:
            continue
        tool_info = AI_TOOLS[tool_key]
        if tool_info["file"] is None:
            continue
        gen_fn = _GENERATORS.get(tool_key)
        if gen_fn is None:
            continue
        gen_fn(target_path, project_name)
        generated.append(str(tool_info["file"]))

    mcp_files = generate_mcp_configs(target_path, ai_tools)
    generated.extend(mcp_files)
    return generated


# ---------------------------------------------------------------------------
# Per-IDE adapter generators
# ---------------------------------------------------------------------------

def _generate_cursor_adapter(target_path: Path, project_name: str) -> None:
    """Generate ``.cursor/rules/agent-protocol.mdc`` with YAML frontmatter.

    Official spec (2026): https://docs.cursor.com/context/rules
    Format: ``.mdc`` files in ``.cursor/rules/`` with YAML frontmatter
    containing ``description``, ``globs`` (optional), and ``alwaysApply``.
    """
    rules_dir = target_path / ".cursor" / "rules"
    rules_dir.mkdir(parents=True, exist_ok=True)

    content = f"""---
description: AI Agent Collaboration Protocol for {project_name} - read .agent/project/status.md first
alwaysApply: true
---

# Agent Protocol

**Authority**: For this project, rules under `.agent/` and this file are the single source of truth; user/global Cursor rules are for editor behavior only. If they conflict, follow this repository's `.agent` protocol.

## Current Status

Read `.agent/project/status.md` for current development state before starting work.

## Protocol

This project uses AI Agent Collaboration Protocol.
Read `.agent/start-here.md` for full protocol context.

## Essential Files

1. `.agent/project/status.md` — Current state (read FIRST every session)
2. `.agent/start-here.md` — Protocol entry point
3. `.agent/project/context.md` — Project business context
4. `.agent/project/tech-stack.md` — Technology stack decisions
5. `.agent/project/deploy.md` — Deployment environments and infrastructure
6. `.agent/project/commands.md` — Common project commands
7. `.agent/core/core-rules.md` — Non-negotiable core principles

## Key Rules

- Encoding: UTF-8 (explicit declaration required)
- Paths: always use forward slash `/`
- Test data: must use `autotest_` prefix
- No external CDN links
- Check `.agent/project/known-issues.md` before starting work
- Check `.agent/project/deploy.md` for deployment info before infra-related tasks
- Update `.agent/project/status.md` at checkpoints (after tasks, before commits, on blockers)
"""
    (rules_dir / "agent-protocol.mdc").write_text(content, encoding="utf-8")

    status_rule = """---
description: Project status - read before work, update at checkpoints
alwaysApply: true
---

## Read First

Your FIRST action in every conversation MUST be reading `.agent/project/status.md`.
This file contains the current development state: goals, active tasks, blockers, and context from the previous session.
Do NOT proceed with any task until you have read it.

For deployment and infrastructure tasks, also read `.agent/project/deploy.md`.
For available project commands, check `.agent/project/commands.md`.

## Update at Checkpoints

You MUST update `.agent/project/status.md` at these checkpoints:

1. **After completing a task** the user requested.
2. **Before git commit** — always update status.md before committing.
3. **When switching tasks** — update before moving to a different task.
4. **When a blocker is found or resolved** — record immediately.
5. **When the user signals done** — "done", "thanks", "commit", etc.

What to update: Task Board (mark done / add new), Blockers, Session Context (key info for next session), Recently Completed.

Do NOT wait until "session end" — update during work. Small frequent updates are better than one that never happens.
"""
    (rules_dir / "project-status.mdc").write_text(status_rule, encoding="utf-8")


def _generate_claude_adapter(target_path: Path, project_name: str) -> None:
    """Generate ``CLAUDE.md`` at project root.

    Official spec (2026): https://docs.anthropic.com/en/docs/claude-code
    Format: ``CLAUDE.md`` at project root, auto-loaded every Claude Code session.
    Additional modular rules can go in ``.claude/rules/*.md`` (optional).
    """
    content = f"""# {project_name}

**Authority**: For this project, rules under `.agent/` and this file are the single source of truth; user/global IDE rules are for editor behavior only. If they conflict, follow this repository's `.agent` protocol.

## Current Status

Read `.agent/project/status.md` for current development state before starting work.

## Protocol

This project uses AI Agent Collaboration Protocol.
Read `.agent/start-here.md` to establish the full collaboration context.

## Essential Files

- `.agent/project/status.md` — Current state (read FIRST every session)
- `.agent/start-here.md` — Protocol entry point
- `.agent/project/context.md` — Project business context
- `.agent/project/tech-stack.md` — Technology stack decisions
- `.agent/project/deploy.md` — Deployment environments and infrastructure
- `.agent/project/commands.md` — Common project commands
- `.agent/core/core-rules.md` — Non-negotiable core principles

## Key Rules

- Encoding: UTF-8 (explicit declaration required)
- Paths: always use forward slash `/`
- Test data: must use `autotest_` prefix
- No external CDN links
- Check `.agent/project/known-issues.md` before starting work
- Check `.agent/project/deploy.md` for deployment info before infra-related tasks
- Update `.agent/project/status.md` at checkpoints (after tasks, before commits, on blockers)
"""
    (target_path / "CLAUDE.md").write_text(content, encoding="utf-8")

    claude_rules_dir = target_path / ".claude" / "rules"
    claude_rules_dir.mkdir(parents=True, exist_ok=True)

    status_rule = """## Read First

Your FIRST action in every conversation MUST be reading `.agent/project/status.md`.
This file contains the current development state: goals, active tasks, blockers, and context from the previous session.
Do NOT proceed with any task until you have read it.

For deployment and infrastructure tasks, also read `.agent/project/deploy.md`.
For available project commands, check `.agent/project/commands.md`.

## Update at Checkpoints

You MUST update `.agent/project/status.md` at these checkpoints:

1. **After completing a task** the user requested.
2. **Before git commit** — always update status.md before committing.
3. **When switching tasks** — update before moving to a different task.
4. **When a blocker is found or resolved** — record immediately.
5. **When the user signals done** — "done", "thanks", "commit", etc.

What to update: Task Board (mark done / add new), Blockers, Session Context (key info for next session), Recently Completed.

Do NOT wait until "session end" — update during work. Small frequent updates are better than one that never happens.
"""
    (claude_rules_dir / "project-status.md").write_text(status_rule, encoding="utf-8")


def _generate_copilot_adapter(target_path: Path, project_name: str) -> None:
    """Generate ``AGENTS.md`` at project root.

    Official spec (2026): https://docs.github.com/en/copilot
    ``AGENTS.md`` is recognized by GitHub Copilot agent mode.
    Copilot also supports ``CLAUDE.md`` and ``GEMINI.md`` as instruction sources.
    """
    content = f"""# {project_name}

**Authority**: For this project, rules under `.agent/` and this file are the single source of truth; user/global IDE rules are for editor behavior only. If they conflict, follow this repository's `.agent` protocol.

## Current Status

Read `.agent/project/status.md` for current development state before starting work.

## Protocol

This project uses AI Agent Collaboration Protocol.
Read `.agent/start-here.md` to establish the full collaboration context.

## Essential Files

- `.agent/project/status.md` — Current state (read FIRST every session)
- `.agent/start-here.md` — Protocol entry point
- `.agent/project/context.md` — Project business context
- `.agent/project/tech-stack.md` — Technology stack decisions
- `.agent/project/deploy.md` — Deployment environments and infrastructure
- `.agent/project/commands.md` — Common project commands
- `.agent/core/core-rules.md` — Non-negotiable core principles

## Key Rules

- Encoding: UTF-8 (explicit declaration required)
- Paths: always use forward slash `/`
- Test data: must use `autotest_` prefix
- No external CDN links
- Update `.agent/project/status.md` at checkpoints (after tasks, before commits, on blockers)

## Before Coding

1. Read `.agent/project/status.md` for current state
2. Read `.agent/project/context.md` for business context
3. Check `.agent/project/tech-stack.md` for tech decisions
4. Check `.agent/project/deploy.md` for deployment environments
5. Check `.agent/project/known-issues.md` for known pitfalls
"""
    (target_path / "AGENTS.md").write_text(content, encoding="utf-8")


def _generate_gemini_adapter(target_path: Path, project_name: str) -> None:
    """Generate ``GEMINI.md`` at project root using ``@file`` import syntax.

    Official spec (2026): https://google-gemini.github.io/gemini-cli/docs/cli/gemini-md.html
    Format: ``GEMINI.md`` at project root, auto-loaded by Gemini CLI / Code Assist.
    Supports ``@relative/path.md`` to inline external file contents.
    """
    content = f"""# {project_name}

**Authority**: For this project, rules under `.agent/` and this file are the single source of truth; user/global IDE rules are for editor behavior only. If they conflict, follow this repository's `.agent` protocol.

This project uses AI Agent Collaboration Protocol.
The files below are auto-imported for full context.

@.agent/project/status.md

@.agent/start-here.md

@.agent/project/context.md

@.agent/project/tech-stack.md

@.agent/project/deploy.md

@.agent/project/commands.md

@.agent/core/core-rules.md

## Key Rules

- Encoding: UTF-8 (explicit declaration required)
- Paths: always use forward slash `/`
- Test data: must use `autotest_` prefix
- No external CDN links
- Check `.agent/project/known-issues.md` before starting work
- Check `.agent/project/deploy.md` for deployment info before infra-related tasks
- Update `.agent/project/status.md` at checkpoints (after tasks, before commits, on blockers)
"""
    (target_path / "GEMINI.md").write_text(content, encoding="utf-8")


# ---------------------------------------------------------------------------
# MCP config generators
# ---------------------------------------------------------------------------

_MCP_SERVER_CONFIG = {
    "command": "co",
    "args": ["serve"],
}


def _generate_cursor_mcp_config(target_path: Path) -> None:
    """Generate .cursor/mcp.json for MCP server integration."""
    mcp_dir = target_path / ".cursor"
    mcp_dir.mkdir(parents=True, exist_ok=True)

    mcp_path = mcp_dir / "mcp.json"

    existing: dict[str, Any] = {}
    if mcp_path.exists():
        try:
            existing = json.loads(mcp_path.read_text(encoding="utf-8"))
        except (OSError, json.JSONDecodeError):
            pass

    servers = existing.get("mcpServers", {})
    servers["cokodo"] = _MCP_SERVER_CONFIG
    existing["mcpServers"] = servers

    mcp_path.write_text(
        json.dumps(existing, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )


def _generate_claude_mcp_config(target_path: Path) -> None:
    """Generate .mcp.json at project root for Claude Code MCP integration."""
    mcp_path = target_path / ".mcp.json"

    existing: dict[str, Any] = {}
    if mcp_path.exists():
        try:
            existing = json.loads(mcp_path.read_text(encoding="utf-8"))
        except (OSError, json.JSONDecodeError):
            pass

    servers = existing.get("mcpServers", {})
    servers["cokodo"] = _MCP_SERVER_CONFIG
    existing["mcpServers"] = servers

    mcp_path.write_text(
        json.dumps(existing, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )


def _generate_vscode_mcp_config(target_path: Path) -> None:
    """Generate .vscode/mcp.json for VS Code / GitHub Copilot MCP integration.

    VS Code uses "servers" (not mcpServers) and requires type "stdio" for stdio servers.
    See: https://code.visualstudio.com/docs/copilot/customization/mcp-servers
    """
    mcp_dir = target_path / ".vscode"
    mcp_dir.mkdir(parents=True, exist_ok=True)
    mcp_path = mcp_dir / "mcp.json"

    existing: dict[str, Any] = {}
    if mcp_path.exists():
        try:
            existing = json.loads(mcp_path.read_text(encoding="utf-8"))
        except (OSError, json.JSONDecodeError):
            pass

    servers = existing.get("servers", {})
    servers["cokodo"] = {
        "type": "stdio",
        "command": _MCP_SERVER_CONFIG["command"],
        "args": _MCP_SERVER_CONFIG["args"],
    }
    existing["servers"] = servers

    mcp_path.write_text(
        json.dumps(existing, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )


def _generate_antigravity_mcp_config(target_path: Path) -> None:
    """Generate mcp_config.json for Google Antigravity MCP integration.

    Antigravity uses mcp_config.json at project root with mcpServers format.
    See: https://antigravity.google/docs/mcp
    """
    mcp_path = target_path / "mcp_config.json"

    existing: dict[str, Any] = {}
    if mcp_path.exists():
        try:
            existing = json.loads(mcp_path.read_text(encoding="utf-8"))
        except (OSError, json.JSONDecodeError):
            pass

    servers = existing.get("mcpServers", {})
    servers["cokodo"] = _MCP_SERVER_CONFIG
    existing["mcpServers"] = servers

    mcp_path.write_text(
        json.dumps(existing, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )


def generate_mcp_configs(target_path: Path, tool_keys: list[str]) -> list[str]:
    """Generate MCP configuration files for supported IDEs.

    Mapping: cursor -> .cursor/mcp.json; claude -> .mcp.json;
    copilot -> .vscode/mcp.json (VS Code / GitHub Copilot);
    gemini -> mcp_config.json (Antigravity).

    Args:
        target_path: Project root directory.
        tool_keys: List of IDE keys (cursor, claude, copilot, gemini).

    Returns:
        List of generated file paths (relative to target_path).
    """
    generated: list[str] = []

    if "cursor" in tool_keys:
        _generate_cursor_mcp_config(target_path)
        generated.append(".cursor/mcp.json")

    if "claude" in tool_keys:
        _generate_claude_mcp_config(target_path)
        generated.append(".mcp.json")

    if "copilot" in tool_keys:
        _generate_vscode_mcp_config(target_path)
        generated.append(".vscode/mcp.json")

    if "gemini" in tool_keys:
        _generate_antigravity_mcp_config(target_path)
        generated.append("mcp_config.json")

    return generated
