"""Tests for alive.dream.dream_log and request logging in alive.core.self_model."""

import json

import pytest

from alive.core.self_model import (
    RequestRecord,
    list_requests,
    record_request,
)
from alive.core.skill_registry import SkillInfo, SkillRegistry
from alive.dream.dream_log import (
    _format_dream_prompt,
    _gather_dream_data,
    display_dream,
    generate_dream,
)
from alive.llm.base import LLMProvider


# ── Mock provider ──────────────────────────────────────────────────────────


class MockProvider(LLMProvider):
    """LLM provider that returns canned responses."""

    name = "mock"

    def __init__(self, responses: list[str] | None = None):
        self._responses = list(responses or [])
        self._call_count = 0

    async def complete(self, system_prompt: str, user_prompt: str) -> str:
        if self._call_count < len(self._responses):
            response = self._responses[self._call_count]
        else:
            response = "## How I've grown\nI have no data yet."
        self._call_count += 1
        return response


# ── Fixtures ────────────────────────────────────────────────────────────────


@pytest.fixture
def registry(tmp_path):
    """Create a registry pointing at a temp directory (no seed skills)."""
    import alive.seeds._installer as installer

    original = installer._SEEDS_DIR
    installer._SEEDS_DIR = tmp_path / "empty_seeds"
    installer._SEEDS_DIR.mkdir()
    reg = SkillRegistry(skills_dir=tmp_path / "skills")
    installer._SEEDS_DIR = original
    return reg


@pytest.fixture
def db_path(tmp_path):
    return tmp_path / "test.db"


@pytest.fixture
def dreams_dir(tmp_path):
    d = tmp_path / "dreams"
    d.mkdir()
    return d


# ── RequestRecord data structure ──────────────────────────────────────────


def test_request_record_defaults():
    rec = RequestRecord(
        id=1, request_text="hello", timestamp="2026-01-01T00:00:00+00:00", status="success"
    )
    assert rec.skills_used == []
    assert rec.skills_built == []


# ── record_request / list_requests ────────────────────────────────────────


def test_record_and_list_requests(db_path):
    record_request("what is 2+2", "success", db_path=db_path)
    record_request("list files", "success", ["shell_run"], db_path=db_path)
    requests = list_requests(days=7, db_path=db_path)
    assert len(requests) == 2
    # Most recent first
    assert requests[0].request_text == "list files"
    assert requests[0].skills_used == ["shell_run"]
    assert requests[1].request_text == "what is 2+2"


def test_list_requests_empty_db(db_path):
    requests = list_requests(db_path=db_path)
    assert requests == []


def test_list_requests_nonexistent_db(tmp_path):
    requests = list_requests(db_path=tmp_path / "nonexistent.db")
    assert requests == []


def test_record_request_with_skills_built(db_path):
    rec = record_request(
        "build fibonacci", "success", [], ["fibonacci_sequence"], db_path=db_path
    )
    assert rec.skills_built == ["fibonacci_sequence"]
    requests = list_requests(db_path=db_path)
    assert requests[0].skills_built == ["fibonacci_sequence"]


# ── _gather_dream_data ────────────────────────────────────────────────────


def test_gather_dream_data_empty(registry, db_path):
    data = _gather_dream_data(registry=registry, db_path=db_path)
    assert data["skills"] == []
    assert data["requests"] == []
    assert data["gaps"] == []


def test_gather_dream_data_with_skills(registry, db_path):
    registry.register(name="web_fetch", code='def run(): return {}', description="Fetch URL")
    record_request("fetch google.com", "success", ["web_fetch"], db_path=db_path)

    data = _gather_dream_data(registry=registry, db_path=db_path)
    assert len(data["skills"]) == 1
    assert len(data["requests"]) == 1
    assert data["requests"][0].request_text == "fetch google.com"


# ── _format_dream_prompt ──────────────────────────────────────────────────


def test_format_dream_prompt_with_data(registry, db_path):
    registry.register(
        name="shell_run", code='def run(): return {}', description="Run a shell command"
    )
    record_request("list files", "success", ["shell_run"], db_path=db_path)

    data = _gather_dream_data(registry=registry, db_path=db_path)
    prompt = _format_dream_prompt(data)

    assert "list files" in prompt
    assert "shell_run" in prompt
    assert "Skills I have (1 total)" in prompt


def test_format_dream_prompt_empty():
    data = {
        "skills": [],
        "requests": [],
        "gaps": [],
        "recent_skills": [],
        "top_skills": [],
        "domain_counts": {},
        "days": 7,
    }
    prompt = _format_dream_prompt(data)
    assert "none recorded yet" in prompt


# ── generate_dream ────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_generate_dream_with_data(registry, db_path, dreams_dir):
    registry.register(name="test_skill", code='def run(): return {}', description="A test")
    record_request("test something", "success", ["test_skill"], db_path=db_path)

    dream_text = (
        "## How I've grown\nI built test_skill.\n"
        "## What you ask me most about\nTesting.\n"
        "## Skills I want to build next\nMore tests.\n"
        "## Skill breeding opportunities\nNone yet.\n"
        "## Something I noticed about your patterns\nYou like testing."
    )
    provider = MockProvider(responses=[dream_text])

    from rich.console import Console

    path = await generate_dream(
        provider=provider,
        registry=registry,
        db_path=db_path,
        dreams_dir=dreams_dir,
        console=Console(quiet=True),
    )
    assert path is not None
    assert path.exists()
    assert path.read_text() == dream_text
    assert path.name.endswith(".md")


@pytest.mark.asyncio
async def test_generate_dream_no_data(registry, db_path, dreams_dir):
    """No skills and no requests → returns None."""
    from rich.console import Console

    provider = MockProvider()
    path = await generate_dream(
        provider=provider,
        registry=registry,
        db_path=db_path,
        dreams_dir=dreams_dir,
        console=Console(quiet=True),
    )
    assert path is None


# ── display_dream ──────────────────────────────────────────────────────────


def test_display_dream_no_crash():
    from rich.console import Console

    display_dream("## Test\nHello world", console=Console(quiet=True))


# ── File saving ────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_dream_saved_to_correct_path(registry, db_path, dreams_dir):
    from datetime import datetime

    registry.register(name="s", code='def run(): return {}', description="x")
    provider = MockProvider(responses=["## Dream\nContent"])

    from rich.console import Console

    path = await generate_dream(
        provider=provider,
        registry=registry,
        db_path=db_path,
        dreams_dir=dreams_dir,
        console=Console(quiet=True),
    )
    today = datetime.now().strftime("%Y-%m-%d")
    assert path is not None
    assert path.name == f"{today}.md"
    assert path.parent == dreams_dir
