# core/downloader.py

import logging
import os

import requests

import rcdl.core.parser as parser
from .api import URL
from .config import Config
from .models import Creator, Video, VideoStatus
from .db import DB
from .downloader_subprocess import ytdlp_subprocess
from .file_io import write_json, load_json
from rcdl.interface.progress import ProgressPrinter


class PostsFetcher:
    def __init__(
        self, url: str, json_path: str, max_page: int = Config.DEFAULT_MAX_PAGE
    ):
        self.url = url
        self.json_path = json_path

        self.page = 0
        self.max_page = max_page

        self.status = 200

    def _request_page(self, url: str) -> requests.Response:
        logging.info(f"RequestEngine url {url}")
        headers = URL.get_headers()
        return requests.get(url, headers=headers)

    def request(self, continue_on_error: bool = False, params: dict = {}):
        while self.status == 200 and self.page < self.max_page:
            o = self.page * Config.POST_PER_PAGE
            params["o"] = o
            url = URL.add_params(self.url, params)

            if Config.DRY_RUN:
                logging.debug(f"DRY-RUN posts fetcher {url} -> {self.json_path}")
                self.page += 1
                continue

            response = self._request_page(url)
            self.status = response.status_code

            if self.status != 200:
                if self.page == 0:
                    logging.error(f"Failed to get {url}")
                else:
                    logging.warning(
                        f"Status code {self.status}. This behavior is expected."
                    )
                if not continue_on_error:
                    break
            else:
                logging.info(f"Response Status Code: {self.status}")
                if self.page > 0:
                    json_data = list(load_json(self.json_path))
                else:
                    json_data = []

                if "posts" in response.json():
                    json_data.extend(response.json()["posts"])
                else:
                    json_data.extend(response.json())

                write_json(self.json_path, json_data, mode="w")

            self.page += 1


class VideoDownloader:
    def __init__(self, total_dl: int = -1):
        self.progress = ProgressPrinter(total_dl)

    def _build_url(self, domain: str, video: Video):
        return URL.get_url_from_file(domain, video.url)

    def _build_output_path(self, video: Video, discover: bool = False):
        if not discover:
            return os.path.join(
                Config.creator_folder(video.creator_id), video.relative_path
            )
        else:
            return os.path.join(Config.DISCOVER_DIR, video.relative_path)

    def _update_db_status(self, result: int, video: Video):
        with DB() as d:
            if result == 0:
                d.set_status(video, VideoStatus.DOWNLOADED)
            else:
                d.set_status(video, VideoStatus.FAILED, fail_count=video.fail_count + 1)

    def _exists(self, path: str) -> bool:
        if os.path.exists(path):
            return True
        return False

    def download(
        self, domain: str, video: Video, write_db: bool = True, discover: bool = False
    ) -> bool:
        url = self._build_url(domain, video)
        path = self._build_output_path(video, discover=discover)

        if Config.DRY_RUN:
            logging.debug(f"DRY-RUN dl {url} -> {path}")
            return True

        if self._exists(path):
            logging.warning("Video was already dl ! This should not happen")
            self.progress.update(ignore=True)  # update progress but not eta

        result = ytdlp_subprocess(url, path)
        if write_db:
            self._update_db_status(result, video)

        self.progress.update()
        self.progress.display()

        if result == 0:
            return True
        return False


def fetch_posts_by_tag(tag: str, max_page: int = Config.DEFAULT_MAX_PAGE) -> dict:
    url = URL.get_posts_page_url_wo_param()
    path = Config.cache_file(tag)
    pf = PostsFetcher(url, str(path), max_page=max_page)
    pf.request(continue_on_error=True, params={"tag": tag})

    return load_json(path)


def fetch_posts_by_creator(creator: Creator) -> dict:
    url = URL.get_creator_post_wo_param(creator)
    path = Config.cache_file(f"{creator.creator_id}_{creator.service}")
    pf = PostsFetcher(url, str(path))
    pf.request()

    return load_json(path)


def refresh_creators_videos(creators: list[Creator]):
    for creator in creators:
        logging.info(
            f"CREATOR {creator.creator_id} from {creator.service} on {creator.domain}"
        )

        posts = fetch_posts_by_creator(creator)
        logging.info(
            f"Found {len(posts)} posts from creator {creator.creator_id}({creator.service})"
        )
        posts_with_videos = parser.filter_posts_with_videos_from_json(
            str(Config.cache_file(f"{creator.creator_id}_{creator.service}"))
        )
        logging.info(
            f"Find {len(posts_with_videos)} posts with videos from creator {creator.creator_id}({creator.service})"
        )
        all_videos = parser.convert_posts_to_videos(posts_with_videos)
        logging.info(f"Converted {len(posts_with_videos)} to {len(all_videos)} videos")

        # put all videos in db
        with DB() as db:
            db.insert_videos(all_videos)


def download_videos_to_be_dl():
    """
    Download videos of all creators in creators.json
    """
    with DB() as db:
        videos = db.query_videos(pending=True)
        # VideoDownloader Engine
        vd = VideoDownloader(total_dl=len(videos))

        for video in videos:
            creator = Creator(
                creator_id=video.creator_id,
                service=video.service,
                domain=video.domain,
                status=None,
            )
            succesful = vd.download(creator.domain, video)
            if not succesful:
                logging.warning("Fail to dl vid")


def discover(tag: str, max_page: int):
    discover_creators(tag, max_page)
    dl_video_from_discover_creators()


def discover_creators(tag: str, max_page: int):
    # download posts with searched tags
    posts = fetch_posts_by_tag(tag, max_page)
    logging.info(f"Find {len(posts)} post")

    path = str(Config.cache_file(tag))
    posts_with_videos = parser.filter_posts_with_videos_from_json(path)
    logging.info(f"Find {len(posts_with_videos)} posts with videos")

    creators = parser.get_creators_from_posts(posts_with_videos)

    # save to csv
    file = os.path.join(Config.DISCOVER_DIR, "discover.csv")
    with open(file, "w") as f:
        for c in creators:
            line = f"{c.creator_id};{c.service};{c.domain};{'to_be_treated'}\n"
            f.write(line)


def dl_video_from_discover_creators():
    # load csv
    file = os.path.join(Config.DISCOVER_DIR, "discover.csv")
    with open(file, "r") as f:
        lines = f.readlines()

    creators = []
    for line in lines:
        line = line.replace("\n", "").strip().split(";")
        creators.append(
            Creator(creator_id=line[0], service=line[1], domain=line[2], status=line[3])
        )

    # get posts
    for creator in creators:
        response = requests.get(
            URL.get_creator_post_wo_param(creator), headers=URL.get_headers()
        )
        if response.status_code != 200:
            print(f"ERROR - Request {URL.get_creator_post_wo_param(creator)}")
        response_posts = response.json()
        posts = parser.filter_posts_with_videos_from_list(response_posts)
        print(f"{len(posts)} found")
        if len(posts) > 5:
            posts = posts[0:5]
            print("Limited posts to 5")

        for post in posts:
            urls = parser.extract_video_urls(post)
            url = URL.get_url_from_file(creator.domain, urls[0])
            filename = f"{post['user']}_{post['id']}.mp4"
            filepath = os.path.join(Config.DISCOVER_DIR, filename)
            ytdlp_subprocess(url, filepath)
