"""CLI command for managing optional ML model downloads.

Usage:
    python -m noteflow.cli models download [--model MODEL]
    python -m noteflow.cli models list
    python -m noteflow.cli models status
"""

import argparse
import subprocess
import sys
from dataclasses import dataclass, field

from rich.console import Console

from noteflow.config.constants import SPACY_MODEL_LG, SPACY_MODEL_SM
from noteflow.infrastructure.logging import configure_logging, get_logger

configure_logging()
logger = get_logger(__name__)
console = Console()

# Constants to avoid magic strings
_DEFAULT_MODEL = "spacy-en"
_LOG_DOWNLOAD_FAILED = "Failed to download %s: %s"
_CMD_DOWNLOAD = "download"


# Registry of available models with their download commands
@dataclass
class ModelInfo:
    """Information about a downloadable model."""

    name: str
    description: str
    feature: str  # Which feature flag enables this
    install_command: list[str]
    check_import: str  # Python import to verify installation


AVAILABLE_MODELS: dict[str, ModelInfo] = {
    _DEFAULT_MODEL: ModelInfo(
        name=_DEFAULT_MODEL,
        description=f"English NER model ({SPACY_MODEL_SM})",
        feature="ner",
        install_command=["python", "-m", "spacy", _CMD_DOWNLOAD, SPACY_MODEL_SM],
        check_import=SPACY_MODEL_SM,
    ),
    "spacy-en-lg": ModelInfo(
        name="spacy-en-lg",
        description=f"English NER model - large ({SPACY_MODEL_LG})",
        feature="ner",
        install_command=["python", "-m", "spacy", _CMD_DOWNLOAD, SPACY_MODEL_LG],
        check_import=SPACY_MODEL_LG,
    ),
}


@dataclass
class ModelStatus:
    """Status of a model's installation."""

    model: ModelInfo
    installed: bool
    error: str | None = None


@dataclass
class DownloadResult:
    """Result of a model download operation."""

    model_name: str
    success: bool
    error: str | None = None


@dataclass
class DownloadReport:
    """Report of all download operations."""

    results: list[DownloadResult] = field(default_factory=list)

    @property
    def success_count(self) -> int:
        """Count of successful downloads.

        Returns:
            Number of results with success=True, 0 if no results.
        """
        return sum(bool(r.success)
               for r in self.results) if self.results else 0

    @property
    def failure_count(self) -> int:
        """Count of failed downloads.

        Returns:
            Number of results with success=False, 0 if no results.
        """
        return sum(not r.success for r in self.results) if self.results else 0


def _check_model_installed(model: ModelInfo) -> ModelStatus:
    """Check if a model is installed.

    Args:
        model: Model information.

    Returns:
        ModelStatus with installation state.
    """
    try:
        __import__(model.check_import)
        return ModelStatus(model=model, installed=True)
    except ImportError as e:
        return ModelStatus(model=model, installed=False, error=str(e))


def _download_model(model: ModelInfo) -> DownloadResult:
    """Download a model using its install command.

    Args:
        model: Model information with install command.

    Returns:
        DownloadResult indicating success or failure.
    """
    logger.info("Downloading %s: %s", model.name, model.description)

    try:
        result = subprocess.run(
            model.install_command,
            capture_output=True,
            text=True,
            check=False,
        )

        if result.returncode != 0:
            error_msg = result.stderr.strip() or result.stdout.strip() or "Unknown error"
            logger.error(_LOG_DOWNLOAD_FAILED, model.name, error_msg)
            return DownloadResult(model_name=model.name, success=False, error=error_msg)

        logger.info("Successfully downloaded %s", model.name)
        return DownloadResult(model_name=model.name, success=True)

    except FileNotFoundError as e:
        error_msg = f"Command not found: {e}"
        logger.error(_LOG_DOWNLOAD_FAILED, model.name, error_msg)
        return DownloadResult(model_name=model.name, success=False, error=error_msg)
    except subprocess.SubprocessError as e:
        error_msg = str(e)
        logger.error(_LOG_DOWNLOAD_FAILED, model.name, error_msg)
        return DownloadResult(model_name=model.name, success=False, error=error_msg)


def _run_download(model_name: str | None) -> int:
    """Execute model download.

    Args:
        model_name: Specific model to download, or None for all.

    Returns:
        Exit code (0 for success, 1 for errors).
    """
    if model_name:
        if model_name not in AVAILABLE_MODELS:
            console.print(f"[red]Unknown model:[/red] {model_name}")
            console.print(f"Available models: {', '.join(AVAILABLE_MODELS.keys())}")
            return 1
        models_to_download = [AVAILABLE_MODELS[model_name]]
    else:
        # Download default models (spacy-en for NER)
        models_to_download = [AVAILABLE_MODELS[_DEFAULT_MODEL]]

    report = DownloadReport()

    for model in models_to_download:
        # Check if already installed
        status = _check_model_installed(model)
        if status.installed:
            logger.info("Model %s is already installed", model.name)
            report.results.append(DownloadResult(model_name=model.name, success=True))
            continue

        result = _download_model(model)
        report.results.append(result)

    console.print("\n[bold]Model Download Report:[/bold]")
    console.print(f"  Successful: {report.success_count}")
    console.print(f"  Failed: {report.failure_count}")

    if report.failure_count > 0:
        console.print("\n[red]Failed downloads:[/red]")
        for result in report.results:
            if not result.success:
                console.print(f"  - {result.model_name}: {result.error}")
        return 1

    logger.info(
        "Model download: successful=%d failed=%d",
        report.success_count,
        report.failure_count,
    )
    return 0


def _list_models() -> int:
    """List available models.

    Returns:
        Exit code (always 0).
    """
    console.print("\n[bold]Available Models:[/bold]")
    console.print("-" * 60)

    for model in AVAILABLE_MODELS.values():
        status = _check_model_installed(model)
        status_str = "[green]✓ installed[/green]" if status.installed else "[dim]○ not installed[/dim]"
        console.print(f"  {model.name}")
        console.print(f"    Description: {model.description}")
        console.print(f"    Feature: {model.feature}")
        console.print(f"    Status: {status_str}")
        console.print()

    return 0


def _show_status() -> int:
    """Show status of all models.

    Returns:
        Exit code (always 0).
    """
    console.print("\n[bold]Model Status:[/bold]")
    console.print("-" * 40)

    installed_count = 0
    total_count = len(AVAILABLE_MODELS)

    for model in AVAILABLE_MODELS.values():
        status = _check_model_installed(model)
        if status.installed:
            installed_count += 1
            console.print(f"  [green]✓[/green] {model.name}: installed")
        else:
            console.print(f"  [dim]○[/dim] {model.name}: not installed")

    console.print("-" * 40)
    console.print(f"  {installed_count}/{total_count} models installed")

    return 0


def main() -> None:
    """Entry point for models CLI."""
    parser = argparse.ArgumentParser(
        description="NoteFlow optional ML model management",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # download command
    download_parser = subparsers.add_parser(_CMD_DOWNLOAD, help="Download ML models")
    download_parser.add_argument(
        "--model",
        choices=list(AVAILABLE_MODELS.keys()),
        help=f"Specific model to download (default: {_DEFAULT_MODEL})",
    )

    # list command
    subparsers.add_parser("list", help="List available models")

    # status command
    subparsers.add_parser("status", help="Show model installation status")

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        sys.exit(1)

    if args.command == _CMD_DOWNLOAD:
        exit_code = _run_download(model_name=args.model)
    elif args.command == "list":
        exit_code = _list_models()
    elif args.command == "status":
        exit_code = _show_status()
    else:
        parser.print_help()
        exit_code = 1

    sys.exit(exit_code)


if __name__ == "__main__":
    main()
