"""loony init — scaffold a new pipeline project with agent templates."""

import shutil
from pathlib import Path

from .types import InitResult

TEMPLATES_DIR = Path(__file__).parent / "templates"

SCHEMA_TEMPLATE = """\
# {name} — Schema Contract
# Declare tables, columns, types, sync modes, measures, and dimensions.
# Validated after each script run.

tables:
  # ─── Raw Tables ──────────────────────────────────────────────────
  # One per script. dlt creates these automatically from API responses.

  # raw_example:
  #   description: "Raw data from Example API"
  #   type: data
  #   sync_mode: merge          # replace | merge | append
  #   primary_key: [id]
  #   columns:
  #     id: {{ type: integer, description: "Unique ID" }}
  #     name: {{ type: text, description: "Item name" }}

  # ─── Staged Tables ──────────────────────────────────────────────
  # Cleaned, typed, deduplicated. Created by transforms/001_staged.sql.

  # stg_example:
  #   description: "Cleaned example data"
  #   type: data
  #   primary_key: [id]
  #   columns:
  #     id: {{ type: integer, description: "Unique ID" }}
  #     name: {{ type: text, description: "Item name" }}

  # ─── Materialized Views ─────────────────────────────────────────
  # Aggregated for querying. These drive the semantic layer.

  # mv_example_summary:
  #   description: "Example summary metrics"
  #   type: data
  #   primary_key: [category]
  #   measures:
  #     total_count:
  #       type: count
  #       description: "Total number of items"
  #   dimensions:
  #     category:
  #       column: category
  #       type: string
  #       description: "Item category"
"""

LOONY_YAML_TEMPLATE = """\
name: {name}
description: ""

schedule:
  # script_name: "0 */6 * * *"    # cron expression

retention: 90d
"""

REQUIREMENTS_TEMPLATE = """\
dlt[postgres]
"""

ENV_EXAMPLE_TEMPLATE = """\
# Secrets for this service.
# Copy to .env.local for local development (gitignored).
# For hosted: loony secrets set KEY=value

# EXAMPLE_API_KEY=your-key-here
"""

GITIGNORE_TEMPLATE = """\
.env
.env.local
__pycache__/
*.pyc
.dlt/
.venv/
"""

EXAMPLE_SCRIPT = """\
\"\"\"Example script — replace with your data source.

Every script must export run(conn) -> {{"rows": N}}.
Use dlt for Extract + Load. See schema.yaml for sync_mode guidance.
\"\"\"

import os

import dlt
from dlt.sources.helpers.rest_client import RESTClient
from dlt.sources.helpers.rest_client.paginators import PageNumberPaginator

BASE_URL = "https://api.example.com"


@dlt.source
def example_source():
    client = RESTClient(
        base_url=BASE_URL,
        paginator=PageNumberPaginator(
            total_path="totalPages",
            page_param="page",
            base_page=1,
        ),
        data_selector="items",
    )

    @dlt.resource(name="raw_example", write_disposition="merge", primary_key=["id"])
    def items():
        for page in client.paginate("/items", params={{"limit": 100}}):
            yield page

    return items


def run(conn):
    pipeline = dlt.pipeline(
        pipeline_name="example",
        destination="postgres",
        dataset_name=os.environ.get("LOONY_SCHEMA", "public"),
    )
    info = pipeline.run(example_source())
    rows = 0
    if info.loads_ids:
        for metrics in info.metrics.values():
            for m in metrics:
                rows += sum(m.get("row_counts", {{}}).values())
    return {{"rows": rows}}
"""

EXAMPLE_TRANSFORM = """\
-- Staged tables: clean, type-cast, and deduplicate raw data.
-- Run in alphabetical order. Must be idempotent.

-- Example:
-- CREATE TABLE IF NOT EXISTS stg_example (
--     id INTEGER NOT NULL,
--     name TEXT,
--     UNIQUE (id)
-- );
--
-- DELETE FROM stg_example;
--
-- INSERT INTO stg_example (id, name)
-- SELECT id, name
-- FROM raw_example;
"""

EXAMPLE_VIEWS = """\
-- Materialized views: aggregated for querying.
-- Always add a unique index for REFRESH CONCURRENTLY.

-- Example:
-- CREATE MATERIALIZED VIEW IF NOT EXISTS mv_example_summary AS
-- SELECT
--     category,
--     COUNT(*) AS total_count
-- FROM stg_example
-- GROUP BY category;
--
-- CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_example_summary
-- ON mv_example_summary (category);
"""

AGENTS = {
    "claude": "claude",
    "cursor": "cursor",
    "codex": "codex",
}


def detect_agent(project_dir: Path) -> str | None:
    """Detect which agent is in use based on existing config files."""
    if (project_dir / ".claude").exists():
        return "claude"
    if (project_dir / ".cursor").exists():
        return "cursor"
    if (project_dir / "AGENTS.md").exists():
        return "codex"
    return None


def init_project(
    name: str, agent: str | None = None, project_dir: Path | None = None, update: bool = False
) -> InitResult:
    """Scaffold a new loony pipeline project.

    When update=True, only updates loony-owned agent templates.
    Never touches user files (scripts, transforms, schema.yaml, etc.).
    """
    base_dir = project_dir or Path.cwd()

    if update:
        # Update mode: work in current directory (must already be a project)
        project_dir = base_dir
    else:
        # Create mode: scaffold into a subdirectory named after the service
        project_dir = base_dir / name
        project_dir.mkdir(parents=True, exist_ok=True)

    created = []
    updated = []
    skipped = []

    if not update:
        # Create directories
        (project_dir / "scripts").mkdir(parents=True, exist_ok=True)
        (project_dir / "transforms").mkdir(parents=True, exist_ok=True)

        # Write core files (don't overwrite if they exist)
        files = {
            "schema.yaml": SCHEMA_TEMPLATE.format(name=name),
            "loony.yaml": LOONY_YAML_TEMPLATE.format(name=name),
            "requirements.txt": REQUIREMENTS_TEMPLATE,
            ".env.example": ENV_EXAMPLE_TEMPLATE,
            ".gitignore": GITIGNORE_TEMPLATE,
            "scripts/example.py": EXAMPLE_SCRIPT,
            "transforms/001_staged.sql": EXAMPLE_TRANSFORM,
            "transforms/002_views.sql": EXAMPLE_VIEWS,
        }

        for path, content in files.items():
            full_path = project_dir / path
            if full_path.exists():
                skipped.append(path)
            else:
                full_path.parent.mkdir(parents=True, exist_ok=True)
                full_path.write_text(content)
                created.append(path)

    # Detect or use specified agent
    agent = agent or detect_agent(project_dir) or "claude"

    # Write agent templates (update=True overwrites loony-owned files only)
    agent_files = _write_agent_templates(agent, project_dir, overwrite=update)
    if update:
        updated = agent_files
    else:
        created.extend(agent_files)

    return {
        "name": name,
        "agent": agent,
        "created": created,
        "updated": updated,
        "skipped": skipped,
    }


def _write_agent_templates(agent: str, project_dir: Path, overwrite: bool = False) -> list[str]:
    """Write agent-specific template files.

    When overwrite=True, replaces loony-owned files (loony.md, loony-* skills).
    Never touches user-created files in the same directories.
    """
    written = []
    shared_guide = (TEMPLATES_DIR / "shared" / "loony.md").read_text()

    if agent == "claude":
        # .claude/rules/loony.md — always ours
        rules_dir = project_dir / ".claude" / "rules"
        rules_dir.mkdir(parents=True, exist_ok=True)
        rules_file = rules_dir / "loony.md"
        if overwrite or not rules_file.exists():
            rules_file.write_text(shared_guide)
            written.append(".claude/rules/loony.md")

        # .claude/skills/loony-* — only update loony-prefixed skills
        skills_src = TEMPLATES_DIR / "claude" / "skills"
        for skill_dir in skills_src.iterdir():
            if skill_dir.is_dir() and skill_dir.name.startswith("loony-"):
                dest = project_dir / ".claude" / "skills" / skill_dir.name
                if overwrite and dest.exists():
                    shutil.rmtree(dest)
                if overwrite or not dest.exists():
                    shutil.copytree(skill_dir, dest)
                    written.append(f".claude/skills/{skill_dir.name}/")

    elif agent == "cursor":
        rules_dir = project_dir / ".cursor" / "rules"
        rules_dir.mkdir(parents=True, exist_ok=True)
        rules_file = rules_dir / "loony.mdc"
        if overwrite or not rules_file.exists():
            content = (
                '---\ndescription: "Loony data pipeline project context"\nalwaysApply: true\n---\n\n' + shared_guide
            )
            rules_file.write_text(content)
            written.append(".cursor/rules/loony.mdc")

    elif agent == "codex":
        agents_file = project_dir / "AGENTS.md"
        if overwrite or not agents_file.exists():
            agents_file.write_text(shared_guide)
            written.append("AGENTS.md")

    return written
