"""Organize notes: generate summaries and classify into projects."""

import logging
import shutil
import subprocess
from datetime import date
from pathlib import Path

from pydantic import BaseModel, Field

from notulist.claude import claude_structured_output
from notulist.notes import Note, NoteMetadata
from notulist.prompts import load_prompt
from notulist.projects import Project

logger = logging.getLogger(__name__)


class DailySummary(BaseModel):
    """Structured output from Claude for a daily summary."""

    title: str = Field(min_length=1)
    summary: str = Field(min_length=1)


class MonthlySummary(BaseModel):
    """Structured output from Claude for a monthly summary."""

    title: str = Field(min_length=1)
    summary: str = Field(min_length=1)


class NoteClassification(BaseModel):
    """Classification result for a single note."""

    note_filename: str
    project_slug: str | None = Field(
        default=None,
        description="The project slug this note belongs to, or null if no match",
    )


class ClassificationResult(BaseModel):
    """Structured output from Claude for classifying a batch of notes."""

    classifications: list[NoteClassification]


def load_today_notes(today_dir: Path) -> list[tuple[Path, Note]]:
    """
    Load all note files from the today directory.

    Parameters
    ----------
    today_dir : Path
        Path to the today/ directory.

    Returns
    -------
    list[tuple[Path, Note]]
        List of (file path, parsed Note) tuples.
    """
    results: list[tuple[Path, Note]] = []
    for path in sorted(today_dir.glob("*.md")):
        note = Note.load(path)
        results.append((path, note))
    return results


def notes_for_date(
    notes: list[tuple[Path, Note]], target_date: date
) -> list[tuple[Path, Note]]:
    """
    Filter notes to those created on a specific date.

    Parameters
    ----------
    notes : list[tuple[Path, Note]]
        Notes to filter.
    target_date : date
        The date to match against.

    Returns
    -------
    list[tuple[Path, Note]]
        Notes with matching ``date_created``.
    """
    return [(p, n) for p, n in notes if n.metadata.date_created == target_date]


def notes_for_month(
    notes: list[tuple[Path, Note]], year: int, month: int
) -> list[tuple[Path, Note]]:
    """
    Filter notes to those created in a specific month.

    Parameters
    ----------
    notes : list[tuple[Path, Note]]
        Notes to filter.
    year : int
        Year to match.
    month : int
        Month to match.

    Returns
    -------
    list[tuple[Path, Note]]
        Notes with matching year and month.
    """
    return [
        (p, n)
        for p, n in notes
        if n.metadata.date_created.year == year
        and n.metadata.date_created.month == month
    ]


def format_notes_for_prompt(notes: list[tuple[Path, Note]]) -> str:
    """
    Format notes for embedding in a prompt template.

    Parameters
    ----------
    notes : list[tuple[Path, Note]]
        Notes to format.

    Returns
    -------
    str
        Formatted string with note filenames, titles, and content.
    """
    parts: list[str] = []
    for path, note in notes:
        parts.append(
            f"### {path.name}: {note.metadata.title}\n\n{note.content}"
        )
    return "\n\n---\n\n".join(parts)


def load_projects(projects_dir: Path) -> list[Project]:
    """
    Load all projects from the projects directory.

    Parameters
    ----------
    projects_dir : Path
        Path to the projects/ directory.

    Returns
    -------
    list[Project]
        List of loaded projects.
    """
    projects: list[Project] = []
    if not projects_dir.exists():
        return projects
    for project_dir in sorted(projects_dir.iterdir()):
        if not project_dir.is_dir() or not (project_dir / "PROJECT.md").exists():
            continue
        projects.append(Project.load(project_dir))
    return projects


def format_projects_for_prompt(projects: list[Project]) -> str:
    """
    Format projects for embedding in a prompt template.

    Parameters
    ----------
    projects : list[Project]
        Projects to format.

    Returns
    -------
    str
        Formatted string with project slugs, names, and descriptions.
    """
    parts: list[str] = []
    for project in projects:
        parts.append(
            f"- **{project.slug}** ({project.metadata.name}): {project.description}"
        )
    return "\n".join(parts)


def generate_daily_summary(repository: Path, target_date: date) -> Path | None:
    """
    Generate a daily summary for the given date from today/ notes.

    Parameters
    ----------
    repository : Path
        Root of the notes repository.
    target_date : date
        The date to summarize.

    Returns
    -------
    Path or None
        Path to the written summary file, or None if no notes found.
    """
    all_notes = load_today_notes(repository / "today")
    day_notes = notes_for_date(all_notes, target_date)
    if not day_notes:
        return None

    prompt_template = load_prompt("daily_summary")
    formatted = format_notes_for_prompt(day_notes)
    prompt = prompt_template.format(notes=formatted)

    result = claude_structured_output(prompt, DailySummary)

    summary_path = repository / "journal" / "daily" / f"{target_date.isoformat()}.md"
    summary_path.parent.mkdir(parents=True, exist_ok=True)
    note = Note(
        metadata=NoteMetadata(date_created=target_date, title=result.title),
        content=result.summary,
    )
    note.save(summary_path)

    return summary_path


def generate_monthly_summary(repository: Path, year: int, month: int) -> Path | None:
    """
    Generate a monthly summary for the given month from today/ notes.

    Parameters
    ----------
    repository : Path
        Root of the notes repository.
    year : int
        Year to summarize.
    month : int
        Month to summarize.

    Returns
    -------
    Path or None
        Path to the written summary file, or None if no notes found.
    """
    all_notes = load_today_notes(repository / "today")
    month_notes = notes_for_month(all_notes, year, month)
    if not month_notes:
        return None

    prompt_template = load_prompt("monthly_summary")
    formatted = format_notes_for_prompt(month_notes)
    prompt = prompt_template.format(notes=formatted)

    result = claude_structured_output(prompt, MonthlySummary)

    summary_path = repository / "journal" / "monthly" / f"{year}-{month:02d}.md"
    summary_path.parent.mkdir(parents=True, exist_ok=True)
    note = Note(
        metadata=NoteMetadata(date_created=date(year, month, 1), title=result.title),
        content=result.summary,
    )
    note.save(summary_path)

    return summary_path


def classify_and_archive_notes(
    repository: Path, notes: list[tuple[Path, Note]]
) -> dict[str, str]:
    """
    Classify notes into projects or archive.

    Parameters
    ----------
    repository : Path
        Root of the notes repository.
    notes : list[tuple[Path, Note]]
        Notes to classify and move.

    Returns
    -------
    dict[str, str]
        Mapping of note filename to destination (``"project:<slug>"`` or
        ``"archive"``). Empty dict if classification fails.
    """
    if not notes:
        return {}

    projects = load_projects(repository / "projects")
    result: dict[str, str] = {}
    notes_by_filename = {path.name: path for path, _ in notes}

    if not projects:
        for path, note in notes:
            _move_to_archive(repository, path, note)
            result[path.name] = "archive"
        return result

    prompt_template = load_prompt("classify_notes")
    formatted_notes = format_notes_for_prompt(notes)
    formatted_projects = format_projects_for_prompt(projects)
    prompt = prompt_template.format(notes=formatted_notes, projects=formatted_projects)

    try:
        classification = claude_structured_output(prompt, ClassificationResult)
    except subprocess.CalledProcessError:
        logger.warning("Classification failed. Notes left in today/ for manual organization.")
        return {}

    project_slugs = {p.slug for p in projects}

    for item in classification.classifications:
        path = notes_by_filename.get(item.note_filename)
        if path is None:
            continue
        note = next(n for p, n in notes if p.name == item.note_filename)

        if item.project_slug and item.project_slug in project_slugs:
            dest = repository / "projects" / item.project_slug / path.name
            shutil.move(str(path), str(dest))
            result[item.note_filename] = f"project:{item.project_slug}"
        else:
            _move_to_archive(repository, path, note)
            result[item.note_filename] = "archive"

    return result


def _move_to_archive(repository: Path, note_path: Path, note: Note) -> None:
    """
    Move a note file to the archive directory.

    Parameters
    ----------
    repository : Path
        Root of the notes repository.
    note_path : Path
        Current path of the note file.
    note : Note
        The note being archived.
    """
    archive_date = note.metadata.date_created.isoformat()
    archive_dir = repository / "archive" / archive_date
    archive_dir.mkdir(parents=True, exist_ok=True)
    shutil.move(str(note_path), str(archive_dir / note_path.name))


def organize(repository: Path) -> None:
    """
    Run the full organize pipeline with Rich progress.

    Discovers all distinct dates in today/, generates daily and monthly
    summaries, then classifies and archives all notes.

    Parameters
    ----------
    repository : Path
        Root of the notes repository.
    """
    from rich.progress import Progress

    all_notes = load_today_notes(repository / "today")

    if not all_notes:
        logger.info("No notes found in today/. Nothing to organize.")
        return

    dates = sorted({n.metadata.date_created for _, n in all_notes})
    months = sorted({(d.year, d.month) for d in dates})

    with Progress() as progress:
        daily_task = progress.add_task("Updating daily summaries...", total=len(dates))
        monthly_task = progress.add_task("Updating monthly summaries...", total=len(months))
        archive_task = progress.add_task("Archiving notes...", total=1)

        for d in dates:
            generate_daily_summary(repository, d)
            progress.update(daily_task, advance=1)

        for year, month in months:
            generate_monthly_summary(repository, year, month)
            progress.update(monthly_task, advance=1)

        classify_and_archive_notes(repository, all_notes)
        progress.update(archive_task, advance=1)
