"""Command-line interface for Kikusan."""

import logging
import os
import shutil
from enum import Enum
from pathlib import Path
from typing import Annotated

import typer

from kikusan.config import get_config
from kikusan.cron.cli import cron_command
from kikusan.download import UnavailableCooldownError, download, download_url
from kikusan.plugins.cli import plugins_app
from kikusan.search import (
    get_charts,
    get_mood_categories,
    get_mood_playlists,
    get_playlist_tracks,
    search,
)

logging.basicConfig(
    level=logging.INFO,
    format="%(message)s",
)


class AudioFormat(str, Enum):
    opus = "opus"
    mp3 = "mp3"
    flac = "flac"


class OrganizationMode(str, Enum):
    flat = "flat"
    album = "album"


class CookieMode(str, Enum):
    auto = "auto"
    always = "always"
    never = "never"


app = typer.Typer(help="Kikusan - Search and download music from YouTube Music.")

explore_app = typer.Typer(help="Explore moods, genres, and charts on YouTube Music.")
app.add_typer(explore_app, name="explore")

app.add_typer(plugins_app, name="plugins")

app.command(name="cron")(cron_command)


def _migrate_legacy_data_dir(data_dir: Path) -> None:
    """Migrate legacy <download_dir>/.kikusan into an explicit data_dir."""
    legacy_data_dir = Path(os.getenv("KIKUSAN_DOWNLOAD_DIR", "./downloads")) / ".kikusan"
    if legacy_data_dir.resolve() == data_dir.resolve():
        return
    if not legacy_data_dir.exists() or not legacy_data_dir.is_dir():
        return

    logger = logging.getLogger(__name__)
    if data_dir.exists():
        if not data_dir.is_dir():
            logger.warning(
                "Skipping legacy data migration because target is not a directory: %s",
                data_dir,
            )
            return
        if any(data_dir.iterdir()):
            logger.info(
                "Skipping legacy data migration because target is not empty: %s",
                data_dir,
            )
            return
        for child in legacy_data_dir.iterdir():
            shutil.move(str(child), str(data_dir / child.name))
        legacy_data_dir.rmdir()
        logger.info("Migrated legacy data from %s to %s", legacy_data_dir, data_dir)
        return

    data_dir.parent.mkdir(parents=True, exist_ok=True)
    shutil.move(str(legacy_data_dir), str(data_dir))
    logger.info("Migrated legacy data from %s to %s", legacy_data_dir, data_dir)


def _version_callback(value: bool):
    if value:
        from importlib.metadata import version

        typer.echo(f"kikusan, version {version('kikusan')}")
        raise typer.Exit()


@app.callback()
def main_callback(
    ctx: typer.Context,
    cookie_mode: Annotated[
        CookieMode | None,
        typer.Option(
            "--cookie-mode",
            envvar="KIKUSAN_COOKIE_MODE",
            help="Cookie usage mode: auto, always, never. Default: auto",
        ),
    ] = None,
    cookie_retry_delay: Annotated[
        float | None,
        typer.Option(
            "--cookie-retry-delay",
            envvar="KIKUSAN_COOKIE_RETRY_DELAY",
            help="Delay in seconds before retrying with cookies. Default: 1.0",
        ),
    ] = None,
    no_log_cookie_usage: Annotated[
        bool,
        typer.Option(
            "--no-log-cookie-usage",
            help="Disable logging of cookie usage statistics",
        ),
    ] = False,
    unavailable_cooldown: Annotated[
        int | None,
        typer.Option(
            "--unavailable-cooldown",
            envvar="KIKUSAN_UNAVAILABLE_COOLDOWN_HOURS",
            help="Hours to wait before retrying unavailable videos (0 = disabled). Default: 168 (7 days)",
        ),
    ] = None,
    lyrics_cache_hours: Annotated[
        int | None,
        typer.Option(
            "--lyrics-cache-hours",
            envvar="KIKUSAN_LYRICS_CACHE_HOURS",
            help="Hours to cache negative lyrics lookups (0 = no expiry). Default: 168 (7 days)",
        ),
    ] = None,
    data_dir: Annotated[
        Path | None,
        typer.Option(
            "--data-dir",
            envvar="KIKUSAN_DATA_DIR",
            help="Data directory for state, cache, and metadata files. Default: <download-dir>/.kikusan",
        ),
    ] = None,
    version: Annotated[
        bool | None,
        typer.Option(
            "--version",
            callback=_version_callback,
            is_eager=True,
            help="Show version and exit.",
        ),
    ] = None,
):
    """Kikusan - Search and download music from YouTube Music."""
    ctx.ensure_object(dict)

    if cookie_mode is not None:
        os.environ["KIKUSAN_COOKIE_MODE"] = cookie_mode.value
    if cookie_retry_delay is not None:
        os.environ["KIKUSAN_COOKIE_RETRY_DELAY"] = str(cookie_retry_delay)
    if no_log_cookie_usage:
        os.environ["KIKUSAN_LOG_COOKIE_USAGE"] = "false"
    if unavailable_cooldown is not None:
        os.environ["KIKUSAN_UNAVAILABLE_COOLDOWN_HOURS"] = str(unavailable_cooldown)
    if lyrics_cache_hours is not None:
        os.environ["KIKUSAN_LYRICS_CACHE_HOURS"] = str(lyrics_cache_hours)
    if data_dir is not None:
        _migrate_legacy_data_dir(data_dir)
        os.environ["KIKUSAN_DATA_DIR"] = str(data_dir)


@app.command(name="search")
def search_cmd(
    query: Annotated[str, typer.Argument(help="Search query")],
    limit: Annotated[
        int, typer.Option("-l", "--limit", help="Maximum number of results")
    ] = 10,
):
    """Search for music on YouTube Music."""
    results = search(query, limit=limit)

    if not results:
        typer.echo("No results found.")
        return

    typer.echo(f"\nFound {len(results)} results:\n")

    for i, track in enumerate(results, 1):
        album_info = f" [{track.album}]" if track.album else ""
        typer.echo(f"{i:2}. {track.title} - {track.artist}{album_info}")
        typer.echo(f"    ID: {track.video_id}  Duration: {track.duration_display}")
        typer.echo()


@app.command(name="download")
def download_cmd(
    video_id: Annotated[
        str | None, typer.Argument(help="YouTube video ID")
    ] = None,
    url: Annotated[
        str | None,
        typer.Option("--url", "-u", help="YouTube, YouTube Music, or Deezer URL"),
    ] = None,
    query: Annotated[
        str | None,
        typer.Option("--query", "-q", help="Search query (downloads first match)"),
    ] = None,
    output: Annotated[
        Path | None, typer.Option("--output", "-o", help="Output directory")
    ] = None,
    audio_format: Annotated[
        AudioFormat | None,
        typer.Option("--format", "-f", help="Audio format (default: opus)"),
    ] = None,
    filename_template: Annotated[
        str | None,
        typer.Option(
            "--filename",
            "-n",
            help="Filename template (default: '%(artist,uploader)s - %(title)s')",
        ),
    ] = None,
    no_lyrics: Annotated[
        bool, typer.Option("--no-lyrics", help="Skip fetching lyrics")
    ] = False,
    playlist_name: Annotated[
        str | None,
        typer.Option(
            "--add-to-playlist",
            "-p",
            help="Add downloaded track(s) to M3U playlist",
        ),
    ] = None,
    organization_mode: Annotated[
        OrganizationMode | None,
        typer.Option(
            "--organization-mode",
            envvar="KIKUSAN_ORGANIZATION_MODE",
            help="File organization: flat (all in one dir) or album (Artist/Year - Album/Track). Default: flat",
        ),
    ] = None,
    use_primary_artist: Annotated[
        bool | None,
        typer.Option(
            "--use-primary-artist/--no-use-primary-artist",
            help="Use only primary artist for folder names in album mode (strips 'feat.', etc.)",
        ),
    ] = None,
    replaygain: Annotated[
        bool | None,
        typer.Option(
            "--replaygain/--no-replaygain",
            help="Apply ReplayGain/R128 loudness normalization tags (requires rsgain)",
        ),
    ] = None,
    allow_ugc: Annotated[
        bool | None,
        typer.Option(
            "--allow-ugc/--no-allow-ugc",
            help="Include UGC (user-generated content) tracks in playlist/chart results. Default: exclude",
        ),
    ] = None,
):
    """Download a track by video ID, URL, or search query.

    Examples:

      kikusan download VIDEO_ID

      kikusan download --url "https://music.youtube.com/watch?v=..."

      kikusan download --url "https://music.youtube.com/playlist?list=..."

      kikusan download --url "https://www.deezer.com/playlist/..."

      kikusan download --query "Bohemian Rhapsody Queen"
    """
    if not video_id and not url and not query:
        typer.echo("Error: One of VIDEO_ID, --url, or --query is required", err=True)
        raise typer.Exit(code=2)

    if replaygain is not None:
        os.environ["KIKUSAN_REPLAYGAIN"] = "true" if replaygain else "false"
    if allow_ugc is not None:
        os.environ["KIKUSAN_ALLOW_UGC"] = "true" if allow_ugc else "false"

    config = get_config()
    output_dir = output if output else config.download_dir
    fmt = audio_format.value if audio_format else config.audio_format
    template = filename_template or config.filename_template
    org_mode = organization_mode.value if organization_mode else config.organization_mode
    primary_artist = use_primary_artist if use_primary_artist is not None else config.use_primary_artist

    try:
        # Search and download first match
        if query:
            results = search(query, limit=1)
            if not results:
                typer.echo(f"Error: No results found for: {query}", err=True)
                raise typer.Exit(code=1)

            track = results[0]
            typer.echo(f"Found: {track.title} - {track.artist}")

            audio_path = download(
                video_id=track.video_id,
                output_dir=output_dir,
                audio_format=fmt,
                filename_template=template,
                fetch_lyrics=not no_lyrics,
                organization_mode=org_mode,
                use_primary_artist=primary_artist,
                apply_replaygain=config.replaygain,
            )
            if audio_path:
                typer.echo(f"Downloaded: {audio_path}")
                if playlist_name:
                    from kikusan.playlist import add_to_m3u

                    add_to_m3u([audio_path], playlist_name, output_dir)
                    typer.echo(f"Added to playlist: {playlist_name}.m3u")
            return

        # Handle URL (YouTube, YouTube Music, or Deezer)
        if url:
            from kikusan.deezer import get_tracks_from_url as get_deezer_tracks_from_url
            from kikusan.deezer import is_deezer_url

            if is_deezer_url(url):
                _download_external_url(
                    url=url,
                    output_dir=output_dir,
                    audio_format=fmt,
                    filename_template=template,
                    fetch_lyrics=not no_lyrics,
                    playlist_name=playlist_name,
                    organization_mode=org_mode,
                    use_primary_artist=primary_artist,
                    source_name="Deezer playlist",
                    get_tracks_from_url=get_deezer_tracks_from_url,
                    apply_replaygain=config.replaygain,
                )
            else:
                result = download_url(
                    url=url,
                    output_dir=output_dir,
                    audio_format=fmt,
                    filename_template=template,
                    fetch_lyrics=not no_lyrics,
                    organization_mode=org_mode,
                    use_primary_artist=primary_artist,
                    apply_replaygain=config.replaygain,
                )

                if isinstance(result, list):
                    typer.echo(f"Downloaded {len(result)} tracks to {output_dir}")
                    if playlist_name and result:
                        from kikusan.playlist import add_to_m3u

                        add_to_m3u(result, playlist_name, output_dir)
                        typer.echo(f"Added {len(result)} track(s) to playlist: {playlist_name}.m3u")
                elif result:
                    typer.echo(f"Downloaded: {result}")
                    if playlist_name:
                        from kikusan.playlist import add_to_m3u

                        add_to_m3u([result], playlist_name, output_dir)
                        typer.echo(f"Added to playlist: {playlist_name}.m3u")
                else:
                    typer.echo("Download completed but could not locate file.")
            return

        # Download by video ID
        audio_path = download(
            video_id=video_id,
            output_dir=output_dir,
            audio_format=fmt,
            filename_template=template,
            fetch_lyrics=not no_lyrics,
            organization_mode=org_mode,
            use_primary_artist=primary_artist,
            apply_replaygain=config.replaygain,
        )

        if audio_path:
            typer.echo(f"Downloaded: {audio_path}")
            if playlist_name:
                from kikusan.playlist import add_to_m3u

                add_to_m3u([audio_path], playlist_name, output_dir)
                typer.echo(f"Added to playlist: {playlist_name}.m3u")
        else:
            typer.echo("Download completed but could not locate file.")

    except UnavailableCooldownError as e:
        typer.echo(str(e))
        return
    except typer.Exit:
        raise
    except Exception as e:
        typer.echo(f"Error: {e}", err=True)
        raise typer.Exit(code=1)


def _download_external_url(
    url: str,
    output_dir: Path,
    audio_format: str,
    filename_template: str,
    fetch_lyrics: bool,
    playlist_name: str | None = None,
    organization_mode: str = "flat",
    use_primary_artist: bool = False,
    source_name: str = "External playlist",
    get_tracks_from_url: callable | None = None,
    apply_replaygain: bool = False,
) -> None:
    """Download tracks from an external playlist source by searching YouTube Music."""
    if get_tracks_from_url is None:
        raise ValueError("get_tracks_from_url callback is required")

    source_tracks = get_tracks_from_url(url)

    if not source_tracks:
        typer.echo(f"No tracks found in {source_name.lower()} URL.")
        return

    typer.echo(f"Found {len(source_tracks)} tracks in {source_name}")

    downloaded = 0
    skipped = 0
    failed = 0
    downloaded_paths = []

    for i, source_track in enumerate(source_tracks, 1):
        typer.echo(
            f"[{i}/{len(source_tracks)}] Searching: {source_track.artist} - {source_track.name}"
        )

        # Search YouTube Music for this track
        results = search(source_track.search_query, limit=1)

        if not results:
            typer.echo(f"  Not found on YouTube Music, skipping")
            failed += 1
            continue

        yt_track = results[0]
        typer.echo(f"  Found: {yt_track.title} - {yt_track.artist}")

        try:
            audio_path = download(
                video_id=yt_track.video_id,
                output_dir=output_dir,
                audio_format=audio_format,
                filename_template=filename_template,
                fetch_lyrics=fetch_lyrics,
                organization_mode=organization_mode,
                use_primary_artist=use_primary_artist,
                apply_replaygain=apply_replaygain,
            )

            if audio_path:
                downloaded_paths.append(audio_path)
                if "Skipping" not in str(audio_path):
                    downloaded += 1
                else:
                    skipped += 1

        except Exception as e:
            typer.echo(f"  Failed: {e}")
            failed += 1

    typer.echo(f"\nCompleted: {downloaded} downloaded, {skipped} skipped, {failed} failed")

    # Add all downloaded tracks to playlist
    if playlist_name and downloaded_paths:
        from kikusan.playlist import add_to_m3u

        add_to_m3u(downloaded_paths, playlist_name, output_dir)
        typer.echo(f"Added {len(downloaded_paths)} track(s) to playlist: {playlist_name}.m3u")


@app.command(name="tag")
def tag(
    directory: Annotated[
        Path, typer.Argument(help="Directory to recursively process", exists=True, file_okay=False)
    ],
    lyrics: Annotated[
        bool,
        typer.Option(
            "--lyrics/--no-lyrics",
            help="Fetch and save lyrics from lrclib.net (default: enabled)",
        ),
    ] = True,
    replaygain: Annotated[
        bool,
        typer.Option(
            "--replaygain/--no-replaygain",
            help="Apply ReplayGain/R128 tags via rsgain (default: enabled)",
        ),
    ] = True,
    metadata: Annotated[
        bool,
        typer.Option(
            "--metadata/--no-metadata",
            help="Enrich missing metadata from YouTube Music (default: enabled)",
        ),
    ] = True,
    dry_run: Annotated[
        bool,
        typer.Option("--dry-run", help="Preview what would be done without making changes"),
    ] = False,
):
    """Tag existing audio files with metadata, lyrics, and ReplayGain.

    Recursively processes .opus, .mp3, .flac files in DIRECTORY.

    Examples:

      kikusan tag /path/to/music

      kikusan tag --no-metadata /path/to/music

      kikusan tag --dry-run /path/to/music
    """
    from kikusan.tagging import tag_directory

    if dry_run:
        typer.echo("[dry-run] Previewing changes only")

    stats = tag_directory(
        directory,
        do_lyrics=lyrics,
        do_replaygain=replaygain,
        do_metadata=metadata,
        dry_run=dry_run,
    )

    typer.echo(f"\nProcessed {stats.files_found} files:")
    if metadata:
        typer.echo(f"  Metadata: {stats.metadata_enriched} enriched, {stats.metadata_skipped} skipped (complete), {stats.metadata_failed} failed")
    if lyrics:
        typer.echo(f"  Lyrics: {stats.lyrics_added} added, {stats.lyrics_skipped} skipped (already exist), {stats.lyrics_not_found} not found, {stats.lyrics_failed} failed")
    if replaygain:
        typer.echo(f"  ReplayGain: {stats.replaygain_applied} applied, {stats.replaygain_skipped} skipped (already exist), {stats.replaygain_failed} failed")
    if stats.errors:
        typer.echo(f"  Errors: {stats.errors}")


# --- Explore command group ---


@explore_app.command(name="moods")
def explore_moods():
    """List available mood & genre categories."""
    try:
        sections = get_mood_categories()
    except Exception as e:
        typer.echo(f"Error: {e}", err=True)
        raise typer.Exit(code=1)

    if not sections:
        typer.echo("No categories found.")
        return

    for section in sections:
        typer.echo(f"\n{section.title}:")
        for cat in section.categories:
            typer.echo(f"  {cat.title}  (params: {cat.params})")


@explore_app.command(name="mood-playlists")
def explore_mood_playlists_cmd(
    params: Annotated[str, typer.Argument(help="Category identifier from 'explore moods'")],
    do_download: Annotated[
        bool,
        typer.Option(
            "--download",
            "-d",
            help="Download all tracks from all playlists in this category",
        ),
    ] = False,
    output: Annotated[
        Path | None, typer.Option("--output", "-o", help="Output directory")
    ] = None,
    audio_format: Annotated[
        AudioFormat | None,
        typer.Option("--format", "-f", help="Audio format (default: opus)"),
    ] = None,
    playlist_name: Annotated[
        str | None,
        typer.Option(
            "--add-to-playlist",
            "-p",
            help="Add downloaded tracks to M3U playlist",
        ),
    ] = None,
    allow_ugc: Annotated[
        bool | None,
        typer.Option(
            "--allow-ugc/--no-allow-ugc",
            help="Include UGC (user-generated content) tracks. Default: exclude",
        ),
    ] = None,
):
    """List playlists for a mood/genre category.

    PARAMS is the category identifier from 'explore moods'.
    Use --download to download all tracks from the playlists.
    """
    if allow_ugc is not None:
        os.environ["KIKUSAN_ALLOW_UGC"] = "true" if allow_ugc else "false"

    config = get_config()

    try:
        playlists = get_mood_playlists(params)
    except Exception as e:
        typer.echo(f"Error: {e}", err=True)
        raise typer.Exit(code=1)

    if not playlists:
        typer.echo("No playlists found.")
        return

    for i, pl in enumerate(playlists, 1):
        author = f" by {pl.author}" if pl.author else ""
        typer.echo(f"{i:2}. {pl.title}{author}")
        typer.echo(f"    Playlist ID: {pl.playlist_id}")

    if do_download:
        for pl in playlists:
            typer.echo(f"\nFetching tracks from: {pl.title}")
            try:
                tracks = get_playlist_tracks(pl.playlist_id, allow_ugc=config.allow_ugc)
            except Exception as e:
                typer.echo(f"  Failed to fetch tracks: {e}")
                continue
            _download_explore_tracks(tracks, output, audio_format, playlist_name)


@explore_app.command(name="charts")
def explore_charts_cmd(
    country: Annotated[
        str,
        typer.Option(
            "--country",
            "-c",
            help="ISO 3166-1 Alpha-2 country code (default: ZZ for global)",
        ),
    ] = "ZZ",
    do_download: Annotated[
        bool,
        typer.Option("--download", "-d", help="Download all chart tracks"),
    ] = False,
    output: Annotated[
        Path | None, typer.Option("--output", "-o", help="Output directory")
    ] = None,
    audio_format: Annotated[
        AudioFormat | None,
        typer.Option("--format", "-f", help="Audio format (default: opus)"),
    ] = None,
    playlist_name: Annotated[
        str | None,
        typer.Option(
            "--add-to-playlist",
            "-p",
            help="Add downloaded tracks to M3U playlist",
        ),
    ] = None,
    allow_ugc: Annotated[
        bool | None,
        typer.Option(
            "--allow-ugc/--no-allow-ugc",
            help="Include UGC (user-generated content) tracks. Default: exclude",
        ),
    ] = None,
):
    """Show current music charts.

    Use --download to download all chart tracks.
    """
    if allow_ugc is not None:
        os.environ["KIKUSAN_ALLOW_UGC"] = "true" if allow_ugc else "false"

    config = get_config()

    try:
        charts = get_charts(country, allow_ugc=config.allow_ugc)
    except Exception as e:
        typer.echo(f"Error: {e}", err=True)
        raise typer.Exit(code=1)

    if charts.tracks:
        typer.echo(f"\nTop Songs ({charts.country}):")
        for track in charts.tracks:
            rank = f"#{track.rank} " if track.rank else ""
            typer.echo(f"  {rank}{track.title} - {track.artist}")
            typer.echo(f"    ID: {track.video_id}")

    if charts.artists:
        typer.echo(f"\nTop Artists ({charts.country}):")
        for artist in charts.artists:
            rank = f"#{artist.rank} " if artist.rank else ""
            typer.echo(f"  {rank}{artist.title}")

    if do_download and charts.tracks:
        typer.echo(f"\nDownloading {len(charts.tracks)} chart tracks...")
        config = get_config()
        output_dir = output if output else config.download_dir
        fmt = audio_format.value if audio_format else config.audio_format
        downloaded_paths = []

        for i, track in enumerate(charts.tracks, 1):
            typer.echo(f"[{i}/{len(charts.tracks)}] Downloading: {track.title} - {track.artist}")
            try:
                audio_path = download(
                    video_id=track.video_id,
                    output_dir=output_dir,
                    audio_format=fmt,
                    filename_template=config.filename_template,
                    fetch_lyrics=True,
                    organization_mode=config.organization_mode,
                    use_primary_artist=config.use_primary_artist,
                    apply_replaygain=config.replaygain,
                )
                if audio_path:
                    downloaded_paths.append(audio_path)
            except UnavailableCooldownError as e:
                typer.echo(f"  Skipped (cooldown): {e}")
            except Exception as e:
                typer.echo(f"  Failed: {e}")

        typer.echo(f"\nDownloaded {len(downloaded_paths)} of {len(charts.tracks)} tracks")
        if playlist_name and downloaded_paths:
            from kikusan.playlist import add_to_m3u
            add_to_m3u(downloaded_paths, playlist_name, output_dir)
            typer.echo(f"Added {len(downloaded_paths)} track(s) to playlist: {playlist_name}.m3u")


def _download_explore_tracks(
    tracks: list,
    output: Path | None,
    audio_format: AudioFormat | None,
    playlist_name: str | None,
) -> None:
    """Download a list of Track objects from explore results."""
    if not tracks:
        typer.echo("  No tracks to download.")
        return

    config = get_config()
    output_dir = output if output else config.download_dir
    fmt = audio_format.value if audio_format else config.audio_format
    downloaded_paths = []

    for i, track in enumerate(tracks, 1):
        typer.echo(f"[{i}/{len(tracks)}] Downloading: {track.title} - {track.artist}")
        try:
            audio_path = download(
                video_id=track.video_id,
                output_dir=output_dir,
                audio_format=fmt,
                filename_template=config.filename_template,
                fetch_lyrics=True,
                organization_mode=config.organization_mode,
                use_primary_artist=config.use_primary_artist,
                apply_replaygain=config.replaygain,
            )
            if audio_path:
                downloaded_paths.append(audio_path)
        except UnavailableCooldownError as e:
            typer.echo(f"  Skipped (cooldown): {e}")
        except Exception as e:
            typer.echo(f"  Failed: {e}")

    typer.echo(f"\nDownloaded {len(downloaded_paths)} of {len(tracks)} tracks")
    if playlist_name and downloaded_paths:
        from kikusan.playlist import add_to_m3u
        add_to_m3u(downloaded_paths, playlist_name, output_dir)
        typer.echo(f"Added {len(downloaded_paths)} track(s) to playlist: {playlist_name}.m3u")


@app.command(name="web")
def web(
    host: Annotated[
        str, typer.Option("--host", help="Host to bind to")
    ] = "0.0.0.0",
    port: Annotated[
        int | None, typer.Option("-p", "--port", help="Port to listen on")
    ] = None,
    cors_origins: Annotated[
        str | None,
        typer.Option(
            "--cors-origins",
            envvar="KIKUSAN_CORS_ORIGINS",
            help="CORS allowed origins (comma-separated, or '*' for all). Default: *",
        ),
    ] = None,
    web_playlist: Annotated[
        str | None,
        typer.Option(
            "--web-playlist",
            envvar="KIKUSAN_WEB_PLAYLIST",
            help="M3U playlist name for web downloads (optional)",
        ),
    ] = None,
    organization_mode: Annotated[
        OrganizationMode | None,
        typer.Option(
            "--organization-mode",
            envvar="KIKUSAN_ORGANIZATION_MODE",
            help="File organization: flat (all in one dir) or album (Artist/Year - Album/Track). Default: flat",
        ),
    ] = None,
    use_primary_artist: Annotated[
        bool | None,
        typer.Option(
            "--use-primary-artist/--no-use-primary-artist",
            help="Use only primary artist for folder names in album mode (strips 'feat.', etc.)",
        ),
    ] = None,
    multi_user: Annotated[
        bool | None,
        typer.Option(
            "--multi-user/--no-multi-user",
            help="Enable per-user M3U playlists via Remote-User header (for reverse proxy SSO). Default: disabled",
        ),
    ] = None,
    replaygain: Annotated[
        bool | None,
        typer.Option(
            "--replaygain/--no-replaygain",
            help="Apply ReplayGain/R128 loudness normalization tags (requires rsgain)",
        ),
    ] = None,
    allow_ugc: Annotated[
        bool | None,
        typer.Option(
            "--allow-ugc/--no-allow-ugc",
            help="Include UGC (user-generated content) tracks in playlist/chart results. Default: exclude",
        ),
    ] = None,
    output: Annotated[
        Path | None,
        typer.Option("--output", "-o", envvar="KIKUSAN_DOWNLOAD_DIR", help="Output directory"),
    ] = None,
):
    """Start the web interface."""
    import uvicorn

    from kikusan.config import get_config

    # Override env vars if CLI flags provided
    if cors_origins is not None:
        os.environ["KIKUSAN_CORS_ORIGINS"] = cors_origins
    if web_playlist is not None:
        os.environ["KIKUSAN_WEB_PLAYLIST"] = web_playlist
    if organization_mode is not None:
        os.environ["KIKUSAN_ORGANIZATION_MODE"] = organization_mode.value
    if use_primary_artist is not None:
        os.environ["KIKUSAN_USE_PRIMARY_ARTIST"] = "true" if use_primary_artist else "false"
    if multi_user is not None:
        os.environ["KIKUSAN_MULTI_USER"] = "true" if multi_user else "false"
    if replaygain is not None:
        os.environ["KIKUSAN_REPLAYGAIN"] = "true" if replaygain else "false"
    if allow_ugc is not None:
        os.environ["KIKUSAN_ALLOW_UGC"] = "true" if allow_ugc else "false"
    if output is not None:
        os.environ["KIKUSAN_DOWNLOAD_DIR"] = str(output)

    config = get_config()
    server_port = port or config.web_port

    typer.echo(f"Starting web server at http://{host}:{server_port}")

    # Configure kikusan loggers to match uvicorn's colored format
    from uvicorn.logging import DefaultFormatter

    kikusan_logger = logging.getLogger("kikusan")
    kikusan_logger.setLevel(logging.INFO)
    kikusan_logger.propagate = False
    handler = logging.StreamHandler()
    handler.setFormatter(DefaultFormatter("%(levelprefix)s %(message)s"))
    kikusan_logger.addHandler(handler)

    from kikusan.web.app import app as web_app

    uvicorn.run(web_app, host=host, port=server_port)


# Entry point for pyproject.toml console_scripts
main = typer.main.get_command(app)

if __name__ == "__main__":
    app()
