"""Readlang CSV format writer.

This module provides a writer for the Readlang CSV import format,
including validation, sanitization, and optional example sentence lookup.
"""

import csv
from pathlib import Path
from typing import List, Optional

from rich.console import Console
from rich.progress import (
    BarColumn,
    Progress,
    SpinnerColumn,
    TaskProgressColumn,
    TextColumn,
)

from ..models import Entry
from ..services.fetcher import ExampleFetcher

_console = Console(stderr=True)


class ReadlangWriter:
    """Writer for Readlang CSV format.

    Readlang format specification:
    - Column 1: word (synonyms separated by ' / ')
    - Column 2: translation (alternatives separated by ' / ')
    - Column 3: example sentence (must contain word from column 1)
    - Column 4: score/practice interval (optional)
    - Column 5: next practice date (optional, YYYY-MM-DD)
    - No header row
    - No newlines in cells
    - Maximum 200 entries per file

    Example Validation:
        Readlang requires that the example sentence contains the exact word
        from column 1 (case-insensitive). If validation fails, the example
        is cleared. When fetch_examples=True, the writer will fetch replacement
        examples from Tatoeba or Leipzig for entries with missing or invalid
        examples.

        This is particularly useful for Language Reactor exports where the
        example sentence may contain an inflected form (e.g., "kävikään")
        rather than the dictionary form (e.g., "käydä").

    Attributes:
        ENTRY_LIMIT: Maximum number of entries per CSV file (Readlang's limit)
    """

    ENTRY_LIMIT = 200

    def __init__(
        self,
        example_fetcher: Optional[ExampleFetcher] = None,
        src_lang: str = "fin",
    ):
        """Initialize the Readlang writer.

        Args:
            example_fetcher: Optional ExampleFetcher for fetching example sentences.
                          If None, lookups will be skipped even if requested.
            src_lang: Source language code for example lookups (default: "fin")
        """
        self.example_fetcher = example_fetcher
        self.src_lang = src_lang

    @property
    def name(self) -> str:
        """Return the writer name."""
        return "readlang"

    @property
    def file_extension(self) -> str:
        """Return the default file extension."""
        return ".csv"

    def write(
        self,
        entries: List[Entry],
        output_path: Path,
        fetch_examples: bool = False,
        prefer: str = "tatoeba",
    ) -> List[Path]:
        """Write entries to Readlang CSV, splitting if needed.

        Validates that example sentences contain the word (case-insensitive).
        Invalid examples are cleared. If fetch_examples=True, replacement
        examples are fetched from Tatoeba or Leipzig for entries with missing
        or invalid example sentences.

        Args:
            entries: List of Entry objects to write
            output_path: Base path for output file
            fetch_examples: Whether to fetch replacement examples from an
                           external API for entries with missing or invalid
                           example sentences
            prefer: Which source to try first — ``"tatoeba"`` (default) or
                    ``"leipzig"``. The other is used as fallback.
                    Only used when fetch_examples=True.

        Returns:
            List of paths to created files (may be multiple if split due to
            the 200-entry limit)

        Raises:
            ValueError: If entries list is empty
        """
        if not entries:
            raise ValueError("No entries to write")

        # First pass: sanitize without fetching to identify which need lookups
        sanitized = [self._sanitize_entry(entry) for entry in entries]

        # Second pass: fetch examples for entries that still lack them
        if fetch_examples and self.example_fetcher:
            needs_lookup = [
                (i, e) for i, e in enumerate(sanitized) if not e.example
            ]
            total = len(needs_lookup)
            if total:
                with Progress(
                    SpinnerColumn(),
                    TextColumn("Fetching examples"),
                    BarColumn(),
                    TaskProgressColumn(),
                    TextColumn("[bold cyan]{task.description}"),
                    console=_console,
                    transient=False,
                ) as progress:
                    task = progress.add_task("", total=total)
                    for i, entry in needs_lookup:
                        word = entry.word.split(" / ")[0].strip()
                        progress.update(task, description=word)
                        new_example = self.example_fetcher.fetch_example(
                            word, self.src_lang, prefer=prefer
                        )
                        if new_example:
                            sanitized[i] = Entry(
                                word=entry.word,
                                translation=entry.translation,
                                example=new_example,
                                score=entry.score,
                                date=entry.date,
                            )
                        progress.advance(task)

        # Split into chunks of ENTRY_LIMIT
        chunks = [
            sanitized[i : i + self.ENTRY_LIMIT]
            for i in range(0, len(sanitized), self.ENTRY_LIMIT)
        ]

        # Write each chunk
        written_paths = []
        for idx, chunk in enumerate(chunks):
            path = self._get_chunk_path(output_path, idx, len(chunks))
            self._write_chunk(chunk, path)
            written_paths.append(path)

            if len(chunks) > 1:
                _console.print(
                    f"[dim]  → split part {idx + 1}/{len(chunks)}: {path}[/dim]"
                )

        # Print summary
        if len(written_paths) == 1:
            _console.print(
                f"[green]✓[/green] Wrote [bold]{len(entries)}[/bold] "
                f"{'entry' if len(entries) == 1 else 'entries'} "
                f"to [bold]{written_paths[0]}[/bold]"
            )
        else:
            _console.print(
                f"[green]✓[/green] Wrote [bold]{len(entries)}[/bold] entries "
                f"across [bold]{len(chunks)}[/bold] files"
            )

        return written_paths

    def _sanitize_entry(self, entry: Entry) -> Entry:
        """Clean and validate entry fields.

        Args:
            entry: The entry to sanitize

        Returns:
            A new Entry with sanitized fields; example is cleared if it does
            not contain the word (external lookup happens separately).
        """
        # Strip newlines and normalize whitespace
        word = entry.word.replace("\n", " ").replace("\r", "").strip()
        translation = (
            entry.translation.replace("\n", " ").replace("\r", "").strip()
        )
        example = entry.example.replace("\n", " ").replace("\r", "").strip()

        # Validate that example contains the first synonym (case-insensitive)
        first_synonym = word.split(" / ")[0].strip()
        context_valid = bool(example) and first_synonym.lower() in example.lower()

        if not context_valid:
            example = ""

        return Entry(
            word=word,
            translation=translation,
            example=example,
            score=entry.score,
            date=entry.date,
        )

    def _write_chunk(self, entries: List[Entry], path: Path) -> None:
        """Write a single chunk to CSV.

        Args:
            entries: List of entries to write
            path: Output file path
        """
        with open(path, "w", newline="", encoding="utf-8") as f:
            writer = csv.writer(f, quoting=csv.QUOTE_MINIMAL)
            for entry in entries:
                record = [entry.word, entry.translation, entry.example]

                # Only add optional columns if at least one has a value
                if entry.score or entry.date:
                    record.append(entry.score)
                    record.append(entry.date)

                writer.writerow(record)

    def _get_chunk_path(
        self, base_path: Path, idx: int, total: int
    ) -> Path:
        """Generate path for chunk, adding (part N) if multiple chunks.

        Args:
            base_path: The base output path
            idx: Chunk index (0-based)
            total: Total number of chunks

        Returns:
            Path for this chunk

        Example:
            >>> writer = ReadlangWriter()
            >>> writer._get_chunk_path(Path("out.csv"), 0, 1)
            PosixPath('out.csv')
            >>> writer._get_chunk_path(Path("out.csv"), 0, 3)
            PosixPath('out (part 1).csv')
            >>> writer._get_chunk_path(Path("out.csv"), 1, 3)
            PosixPath('out (part 2).csv')
        """
        if total == 1:
            return base_path

        stem = base_path.stem
        suffix = base_path.suffix or ".csv"
        return base_path.parent / f"{stem} (part {idx + 1}){suffix}"
