from __future__ import annotations

import argparse
import logging

from .addons.nuvio_addon import run_nuvio_addon
from .config import load_yaml
from .crawlers import build_crawler
from .database import MovieDatabase
from .http import HttpClient
from .jellyfin.scan_bot import JellyfinScanBot
from .nfo_writer import NfoWriter
from .playlist.cleaner import PlaylistCleaner
from .playlist.playlist_server import run_playlist_server
from .playlist_writer import PlaylistWriter
from .remap import SlugRemapper
from .strm_writer import StrmWriter
from .tmdb_sync import TmdbSync
from .utils import setup_logging


def load_source_configs(app_config: dict) -> dict[str, dict]:
    result: dict[str, dict] = {}
    for item in app_config.get("sources", []):
        source_config = load_yaml(item["config"])
        result[item["name"]] = source_config
    return result


def has_playable_links(movie) -> bool:
    return any(episode.m3u8_url for server in movie.servers for episode in server.episodes)


def crawl_once(config_path: str, only_source: str | None = None, only_slug: str | None = None) -> None:
    app_config = load_yaml(config_path)
    setup_logging(app_config.get("logging", {}).get("dir", "logs"), app_config.get("logging", {}).get("level", "INFO"))
    log = logging.getLogger("moviebot")
    http = HttpClient(**app_config.get("http", {}))
    db = MovieDatabase(app_config["database"]["path"])
    cleaner = PlaylistCleaner(load_yaml(app_config["cleaner_config"]))
    db_mounts = app_config.get("db_mounts", {})
    playlist_writer = PlaylistWriter(app_config["paths"]["playlists"], http, cleaner, db_mounts.get("playlists", "/m3u8"))
    strm_writer = StrmWriter(app_config["paths"]["libraries"], app_config["playlist_server"]["public_url"], db_mounts.get("libraries", "/strm"))
    nfo_writer = NfoWriter(app_config["paths"]["libraries"], db_mounts.get("libraries", "/strm"))
    max_pages = int(app_config.get("crawl", {}).get("max_pages", 1))

    try:
        for source_name, source_config in load_source_configs(app_config).items():
            if only_source and source_name != only_source:
                continue
            crawler = build_crawler(source_name, source_config, http)
            log.info("Crawling source=%s max_pages=%s", source_name, max_pages)
            slugs = [only_slug] if only_slug else crawler.list_slugs(max_pages)
            for slug in slugs:
                try:
                    movie = crawler.fetch_movie(slug)
                    if not has_playable_links(movie):
                        log.info("Skipped movie without m3u8 links source=%s slug=%s title=%s", source_name, slug, movie.title)
                        continue
                    movie_id = db.upsert_movie(movie)
                    movie = db.apply_canonical_movie(movie_id, movie)
                    has_success_episode = False
                    for server in movie.servers:
                        server_id = db.upsert_server(movie_id, movie.source, server)
                        for episode in server.episodes:
                            episode_id = db.upsert_episode(movie_id, server_id, movie.source, episode)
                            row = db.get_episode(episode_id)
                            if row["crawl_status"] == "success" and row["origin_m3u8_url"] == episode.m3u8_url:
                                has_success_episode = True
                                if movie.media_type == "movie" and row["strm_path"]:
                                    nfo_writer.write_for_db_strm(movie, row["strm_path"])
                                log.info(
                                    "Skipped unchanged m3u8 source=%s slug=%s episode=%s",
                                    source_name,
                                    slug,
                                    episode.filename,
                                )
                                continue
                            try:
                                playlist_result = playlist_writer.write(movie, episode.filename, episode.m3u8_url, row["access_key"])
                                strm_result = strm_writer.write(
                                    movie,
                                    episode.filename,
                                    playlist_result.db_path,
                                    row["access_key"],
                                    episode.season,
                                    episode.episode,
                                )
                                db.update_episode_files(episode_id, playlist_result.db_path, strm_result.db_path, "success")
                                if movie.media_type == "movie":
                                    nfo_writer.write_for_strm(movie, strm_result.file_path)
                                has_success_episode = True
                            except Exception as exc:
                                db.update_episode_files(episode_id, None, None, "failed", str(exc))
                                log.exception("Episode write failed source=%s slug=%s episode=%s", source_name, slug, episode.filename)
                    if has_success_episode and movie.media_type == "tv":
                        nfo_writer.write(movie)
                    log.info("Crawled movie source=%s slug=%s title=%s", source_name, slug, movie.title)
                except Exception as exc:
                    db.mark_source_failed(source_name, slug, str(exc))
                    log.exception("Movie crawl failed source=%s slug=%s", source_name, slug)
        if app_config.get("jellyfin", {}).get("scan_after_crawl", False):
            JellyfinScanBot(app_config.get("jellyfin", {}), http).scan()
    finally:
        db.close()


def main() -> None:
    parser = argparse.ArgumentParser(description="Modular movie crawler bot")
    parser.add_argument("command", choices=["crawl", "serve-playlists", "serve-nuvio-addon", "remap-slugs", "sync-tmdb"])
    parser.add_argument("--config", default="app.yaml")
    parser.add_argument("--source")
    parser.add_argument("--slug")
    parser.add_argument("--tmdb-id")
    args = parser.parse_args()

    if args.command == "crawl":
        crawl_once(args.config, args.source, args.slug)
    elif args.command == "serve-playlists":
        app_config = load_yaml(args.config)
        setup_logging(app_config.get("logging", {}).get("dir", "logs"), app_config.get("logging", {}).get("level", "INFO"))
        server = app_config["playlist_server"]
        run_playlist_server(
            app_config["paths"].get("content_root", "."),
            server.get("host", "0.0.0.0"),
            int(server.get("port", 8000)),
            app_config["database"]["path"],
        )
    elif args.command == "serve-nuvio-addon":
        app_config = load_yaml(args.config)
        setup_logging(app_config.get("logging", {}).get("dir", "logs"), app_config.get("logging", {}).get("level", "INFO"))
        addon_config = app_config.get("nuvio_addon", {}).copy()
        addon_config.setdefault("db_path", app_config["database"]["path"])
        addon_config.setdefault("content_root", app_config["paths"].get("content_root", "."))
        addon_config.setdefault("playlist_mount", app_config.get("db_mounts", {}).get("playlists", "/m3u8"))
        run_nuvio_addon(addon_config)
    elif args.command == "remap-slugs":
        app_config = load_yaml(args.config)
        setup_logging(app_config.get("logging", {}).get("dir", "logs"), app_config.get("logging", {}).get("level", "INFO"))
        slug_maps = app_config.get("slug_maps", {})
        http = HttpClient(**app_config.get("http", {}))
        SlugRemapper(app_config, http).run(
            slug_maps.get("movies", "slug_map_movies.yaml"),
            slug_maps.get("tvshows", "slug_map_tvshows.yaml"),
            args.slug,
        )
    elif args.command == "sync-tmdb":
        app_config = load_yaml(args.config)
        setup_logging(app_config.get("logging", {}).get("dir", "logs"), app_config.get("logging", {}).get("level", "INFO"))
        http = HttpClient(**app_config.get("http", {}))
        TmdbSync(app_config, http).run(args.tmdb_id)


if __name__ == "__main__":
    main()
