from __future__ import annotations

import argparse
import pathlib
import shutil
import sqlite3


def inside(path: pathlib.Path, base: pathlib.Path) -> bool:
    try:
        path.resolve().relative_to(base.resolve())
        return True
    except ValueError:
        return False


def from_db_path(value: str | None, root: pathlib.Path, strm_root: pathlib.Path, m3u8_root: pathlib.Path) -> pathlib.Path | None:
    if not value:
        return None
    text = str(value).replace("\\", "/").strip()
    if text.startswith("/strm/"):
        return (strm_root / text[len("/strm/") :]).resolve()
    if text.startswith("/m3u8/"):
        return (m3u8_root / text[len("/m3u8/") :]).resolve()
    return (root / text.lstrip("/")).resolve()


def purge_source(source: str, db_path: str, strm_dir: str, m3u8_dir: str, skip_files: bool = False) -> dict[str, int | tuple[int, int, int]]:
    root = pathlib.Path.cwd().resolve()
    db = (root / db_path).resolve()
    strm_root = (root / strm_dir).resolve()
    m3u8_root = (root / m3u8_dir).resolve()
    source_m3u8_dir = (m3u8_root / source).resolve()

    if not inside(db, root):
        raise ValueError(f"DB path is outside workspace: {db}")
    if not inside(strm_root, root):
        raise ValueError(f"STRM path is outside workspace: {strm_root}")
    if not inside(m3u8_root, root):
        raise ValueError(f"M3U8 path is outside workspace: {m3u8_root}")

    conn = sqlite3.connect(db)
    conn.row_factory = sqlite3.Row
    conn.executescript(
        """
        CREATE INDEX IF NOT EXISTS idx_movie_sources_source ON movie_sources(source);
        CREATE INDEX IF NOT EXISTS idx_movie_sources_movie_id ON movie_sources(movie_id);
        CREATE INDEX IF NOT EXISTS idx_servers_source ON servers(source);
        CREATE INDEX IF NOT EXISTS idx_episodes_source ON episodes(source);
        CREATE INDEX IF NOT EXISTS idx_episodes_movie_id ON episodes(movie_id);
        """
    )
    rows = conn.execute(
        "SELECT id, movie_id, strm_path, cleaned_m3u8_path FROM episodes WHERE source = ?",
        (source,),
    ).fetchall()
    touched_movies = {int(row["movie_id"]) for row in rows}

    deleted_strm = 0
    deleted_nfo = 0
    deleted_m3u8 = 0
    missing_strm = 0
    touched_dirs: set[pathlib.Path] = set()

    if not skip_files:
        for row in rows:
            strm_path = from_db_path(row["strm_path"], root, strm_root, m3u8_root)
            if strm_path and inside(strm_path, strm_root) and strm_path.suffix.lower() == ".strm":
                touched_dirs.add(strm_path.parent)
                if strm_path.exists():
                    strm_path.unlink()
                    deleted_strm += 1
                else:
                    missing_strm += 1
                nfo_path = strm_path.with_suffix(".nfo")
                if inside(nfo_path, strm_root) and nfo_path.exists():
                    nfo_path.unlink()
                    deleted_nfo += 1

            m3u8_path = from_db_path(row["cleaned_m3u8_path"], root, strm_root, m3u8_root)
            if m3u8_path and inside(m3u8_path, m3u8_root) and m3u8_path.suffix.lower() == ".m3u8" and m3u8_path.exists():
                m3u8_path.unlink()
                deleted_m3u8 += 1

        if inside(source_m3u8_dir, m3u8_root) and source_m3u8_dir.name.lower() == source.lower() and source_m3u8_dir.exists():
            shutil.rmtree(source_m3u8_dir)

        for directory in sorted(touched_dirs, key=lambda path: len(path.parts), reverse=True):
            current = directory
            while inside(current, strm_root) and current != strm_root:
                try:
                    current.rmdir()
                except OSError:
                    break
                current = current.parent

    source_movies = {
        int(row[0])
        for row in conn.execute(
            "SELECT DISTINCT movie_id FROM movie_sources WHERE source = ? AND movie_id IS NOT NULL",
            (source,),
        ).fetchall()
    }
    touched_movies |= source_movies

    conn.execute("DELETE FROM episodes WHERE source = ?", (source,))
    conn.execute("DELETE FROM servers WHERE source = ?", (source,))
    conn.execute("DELETE FROM movie_sources WHERE source = ?", (source,))

    conn.execute("CREATE TEMP TABLE purge_touched_movies (id INTEGER PRIMARY KEY)")
    conn.executemany(
        "INSERT OR IGNORE INTO purge_touched_movies (id) VALUES (?)",
        ((movie_id,) for movie_id in touched_movies),
    )
    before_orphans = conn.total_changes
    conn.execute(
        """
        DELETE FROM movies
        WHERE id IN (SELECT id FROM purge_touched_movies)
          AND NOT EXISTS (SELECT 1 FROM movie_sources WHERE movie_sources.movie_id = movies.id)
          AND NOT EXISTS (SELECT 1 FROM episodes WHERE episodes.movie_id = movies.id)
        """
    )
    orphan_deleted = conn.total_changes - before_orphans

    conn.commit()
    remaining = (
        conn.execute("SELECT COUNT(1) FROM movie_sources WHERE source = ?", (source,)).fetchone()[0],
        conn.execute("SELECT COUNT(1) FROM servers WHERE source = ?", (source,)).fetchone()[0],
        conn.execute("SELECT COUNT(1) FROM episodes WHERE source = ?", (source,)).fetchone()[0],
    )
    conn.close()

    return {
        "deleted_strm": deleted_strm,
        "deleted_nfo": deleted_nfo,
        "deleted_m3u8_files": deleted_m3u8,
        "deleted_orphan_movies": orphan_deleted,
        "missing_strm": missing_strm,
        "remaining_ophim": remaining,
    }


def main() -> None:
    parser = argparse.ArgumentParser(description="Purge one crawled source from DB and generated files.")
    parser.add_argument("source")
    parser.add_argument("--db", default="data/moviebot.db")
    parser.add_argument("--strm-dir", default="strm")
    parser.add_argument("--m3u8-dir", default="m3u8")
    parser.add_argument("--skip-files", action="store_true")
    args = parser.parse_args()

    result = purge_source(args.source, args.db, args.strm_dir, args.m3u8_dir, args.skip_files)
    for key, value in result.items():
        print(f"{key}: {value}")


if __name__ == "__main__":
    main()
