# core/db.py

import sqlite3
from typing import Optional
import logging

from .config import Config
from .models import Video, VideoStatus


class DB:
    def __init__(self):
        self.conn = sqlite3.connect(Config.DB_PATH)
        self.conn.row_factory = sqlite3.Row
        self._init_table()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def _init_table(self):
        # init table for videos to DL
        q = """
            CREATE TABLE IF NOT EXISTS videos (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                post_id TEXT,
                creator_id TEXT,
                service TEXT,
                domain TEXT,
                relative_path TEXT,
                url TEXT,
                part TEXT,
                status TEXT DEFAULT 'not_downloaded',
                fail_count INTEGER DEFAULT 0,
                published TEXT,
                title TEXT,
                substring TEXT,
                downloaded_at TEXT,
                file_size REAL,
                UNIQUE (service, url)
            )
        """
        self.conn.execute(q)
        self.conn.commit()

    def get_video_status(self, video: Video) -> Optional[dict]:
        cur = self.conn.cursor()
        cur.execute(
            "SELECT status, fail_count FROM videos WHERE (service, url) = (?, ?)",
            (video.service, video.url),
        )
        row = cur.fetchone()
        if row:
            return {
                "status": VideoStatus(row["status"]),
                "fail_count": row["fail_count"],
                "downloaded_at": row["downloaded_at"],
                "relative_path": row["relative_path"],
            }
        return None

    def get_videos_by_status(self, status: VideoStatus) -> list[Video]:
        cur = self.conn.cursor()
        cur.execute(
            "SELECT * FROM videos WHERE status = ?",
            (status.value,),
        )
        rows = cur.fetchall()
        videos: list[Video] = []
        for row in rows:
            v = Video(
                post_id=row["post_id"],
                creator_id=row["creator_id"],
                service=row["service"],
                domain=row["domain"],
                relative_path=row["relative_path"],
                url=row["url"],
                part=row["part"],
                status=VideoStatus(row["status"]),
                fail_count=row["fail_count"],
                published=row["published"],
                title=row["title"],
                substring=row["substring"],
                downloaded_at=row["downloaded_at"],
                file_size=row["file_size"],
            )
            videos.append(v)
        logging.info(f"DB request status {status.value} returned {len(videos)} results")
        return videos

    def get_videos_by_creator_id(self, creator_id: str):
        # discover_videos
        cur = self.conn.cursor()
        cur.execute("SELECT * FROM videos WHERE creator_id = ?", (creator_id,))

        rows = cur.fetchall()
        videos: list[Video] = []
        for row in rows:
            v = Video(
                post_id=row["post_id"],
                creator_id=row["creator_id"],
                service=row["service"],
                domain=row["domain"],
                relative_path=row["relative_path"],
                url=row["url"],
                part=row["part"],
                status=VideoStatus(row["status"]),
                fail_count=row["fail_count"],
                published=row["published"],
                title=row["title"],
                substring=row["substring"],
                downloaded_at=row["downloaded_at"],
                file_size=row["file_size"],
            )
            videos.append(v)
        return videos

    def mark_not_downloaded(self, video: Video):
        video.status = VideoStatus.NOT_DOWNLOADED
        self._upsert_video(video)

    def mark_downloaded(self, video: Video):
        video.status = VideoStatus.DOWNLOADED
        self._upsert_video(video)

    def mark_failed(self, video: Video, fail_count: int):
        video.status = VideoStatus.FAILED
        video.fail_count = fail_count
        self._upsert_video(video)

    def mark_skipped(self, video: Video):
        video.status = VideoStatus.SKIPPED
        self._upsert_video(video)

    def mark_ignored(self, video: Video):
        video.status = VideoStatus.IGNORED
        self._upsert_video(video)

    def _upsert_video(self, video: Video):
        q = """
            INSERT INTO videos (
                post_id, creator_id, service, domain, relative_path, url, part, 
                status, fail_count, published, title, substring, 
                downloaded_at, file_size
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(service, url) DO UPDATE SET
                status=excluded.status,
                fail_count=excluded.fail_count,
                relative_path=excluded.relative_path,
                downloaded_at=excluded.downloaded_at,
                file_size=excluded.file_size
        """
        if video.status is None:
            video.status = VideoStatus.NOT_DOWNLOADED

        self.conn.execute(
            q,
            (
                video.post_id,
                video.creator_id,
                video.service,
                video.domain,
                video.relative_path,
                video.url,
                video.part,
                video.status.value,
                video.fail_count,
                video.published,
                video.title,
                video.substring,
                video.downloaded_at,
                video.file_size,
            ),
        )
        self.conn.commit()

    def get_pending_videos(
        self, max_fail_count: int = Config.MAX_FAIL_COUNT
    ) -> list[Video]:
        cur = self.conn.cursor()
        cur.execute(
            "SELECT * FROM videos WHERE status = ? OR (status = ? AND fail_count < ?)",
            (
                VideoStatus.NOT_DOWNLOADED,
                VideoStatus.FAILED,
                max_fail_count,
            ),
        )

        rows = cur.fetchall()
        videos: list[Video] = []

        for row in rows:
            videos.append(
                Video(
                    post_id=row["post_id"],
                    creator_id=row["creator_id"],
                    service=row["service"],
                    domain=row["domain"],
                    relative_path=row["relative_path"],
                    url=row["url"],
                    part=row["part"],
                    status=VideoStatus(row["status"]),
                    fail_count=row["fail_count"],
                    published=row["published"],
                    title=row["title"],
                    substring=row["substring"],
                    downloaded_at=row["downloaded_at"],
                    file_size=row["file_size"],
                )
            )

        logging.info(
            "DB pending videos: %d (max_fail_count=%d)", len(videos), max_fail_count
        )

        return videos

    def close(self):
        self.conn.close()


def update_db(videos: list[Video]):
    if not videos:
        return

    with DB() as d:
        q = """
            INSERT OR IGNORE INTO videos (
                post_id, creator_id, service, domain, relative_path, url, part, 
                status, fail_count, published, title, substring, 
                downloaded_at, file_size
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """

        rows = []
        for video in videos:
            rows.append(
                (
                    video.post_id,
                    video.creator_id,
                    video.service,
                    video.domain,
                    video.relative_path,
                    video.url,
                    video.part,
                    VideoStatus.NOT_DOWNLOADED.value,
                    0,
                    video.published,
                    video.title,
                    video.substring,
                    None,
                    None,
                )
            )

        d.conn.executemany(q, rows)
        d.conn.commit()
