from __future__ import annotations
import os
import json
import logging
import threading

from httpx import Response
from functools import cached_property
from base_api.modules.config import RuntimeConfig
from base_api.base import BaseCore, setup_logger, Helper
from typing import List, Generator, Optional, Literal, Dict, Tuple


try:
    from .modules.consts import *
    from .modules.errors import *

except (ModuleNotFoundError, ImportError):
    from modules.consts import *
    from modules.errors import *

try:
    import lxml
    parser = "lxml"

except (ModuleNotFoundError, ImportError):
    parser = "html.parser"


def parse_bitrate_from_url(url: str) -> int | None:
    """
    Try to pull something like 4000K or 2000k out of the URL and convert to bps.
    """
    m = re.search(r'(\d+)\s*[kK](?![a-zA-Z])', url)
    if m:
        return int(m.group(1)) * 1000
    return None


def variant_key(item: Dict) -> Tuple[int, str]:
    """
    Sort key: default first (False>True trick with tuple), then by quality descending.
    We invert defaultQuality so True sorts before False.
    """
    return (0 if item.get("defaultQuality") else 1, -(int(item.get("quality", 0))))


def build_master_playlist(variants: List[Dict]) -> str:
    """
    Build an HLS master playlist string from a list of dicts like the one you provided.
    Each item should have: defaultQuality (bool), format (e.g., 'hls'), videoUrl, quality (e.g., '720').
    """
    # Keep only HLS entries with a URL
    items = [v for v in variants if v.get("format") == "hls" and v.get("videoUrl")]
    if not items:
        raise ValueError("No HLS variants found")

    # Sort: default first, then highest quality
    items.sort(key=variant_key)

    lines = ["#EXTM3U", "#EXT-X-VERSION:3"]

    for v in items:
        q = int(v.get("quality", 0))
        w, h = RES_BY_QUALITY.get(q, (0, 0))
        bw = parse_bitrate_from_url(v["videoUrl"]) or BPS_FALLBACK.get(q, 1_000_000)

        # You can add CODECS, FRAME-RATE, AUDIO, SUBTITLES if you know them.
        attrs = [
            f"BANDWIDTH={bw}",
            f"AVERAGE-BANDWIDTH={bw}",
        ]
        if w and h:
            attrs.append(f"RESOLUTION={w}x{h}")
        # Add NAME (for players that show labels)
        attrs.append(f'NAME="{q}p"')

        lines.append(f"#EXT-X-STREAM-INF:{','.join(attrs)}")
        lines.append(v["videoUrl"])

    return "\n".join(lines) + "\n"


class Channel(Helper):
    def __init__(self, url: str, core: BaseCore):
        super(Channel, self).__init__(core, video=Video)
        self.url = url
        self.core = core
        self.html_content = self.core.fetch(url)
        self.soup = BeautifulSoup(self.html_content, parser)
        self.channel_info_box = self.soup.find("div", class_="channel-sideBar")

    @cached_property
    def name(self) -> str:
        return self.soup.find("h1", class_="title-text").text.replace("Subscribe", "").strip()

    @cached_property
    def channel_rank(self) -> str:
        return self.channel_info_box.find("div", class_="channel-rank").find("div", class_="rank").text.strip()

    @cached_property
    def total_videos_count(self) -> str:
        return self.channel_info_box.find("div", class_="channel-info").find("div", class_="info-metrics").text.strip()

    @cached_property
    def channel_view_count(self) -> str:
        return self.channel_info_box.find_all("div", class_="channel-info")[1].find("div", class_="info-metrics").text.strip()

    @cached_property
    def channel_subscribers_count(self) -> str:
        return self.channel_info_box.find_all("div", class_="channel-info")[2].find("div",
                                                                                    class_="info-metrics").text.strip()

    @cached_property
    def description(self) -> str:
        return self.soup.find("div", class_="channel-description").find("p").text.strip()

    def videos(self, pages: int = 2, videos_concurrency: int = None, pages_concurrency: int = None):
        page_urls = [f"{self.url}?page={page}" for page in range(1, pages + 1)]
        videos_concurrency = videos_concurrency or self.core.config.videos_concurrency
        pages_concurrency = pages_concurrency or self.core.config.pages_concurrency
        yield from self.iterator(page_urls=page_urls, videos_concurrency=videos_concurrency, pages_concurrency=pages_concurrency,
                                 extractor=extractor_html)


class Collection(Helper):
    def __init__(self, url: str, core: BaseCore):
        super(Collection, self).__init__(core, video=Video)
        self.url = url
        self.core = core
        self.html_content = self.core.fetch(self.url)
        self.soup = BeautifulSoup(self.html_content, parser)

    @cached_property
    def name(self) -> str:
        return self.soup.find("div", class_="top-section").find("h4").text.replace("Collection:", "").strip()

    @cached_property
    def rating(self) -> str:
        return self.soup.find("div", class_="featureCollectionRating").text.strip()

    @cached_property
    def total_videos_count(self) -> str:
        return self.soup.find("p", class_="collection-videos-count").text.strip()

    @cached_property
    def view_count(self) -> str:
        return self.soup.find("div", class_="top-section").find_all("li")[1].find("p").text.strip()

    @cached_property
    def last_updated(self) -> str:
        return self.soup.find("li", class_="lastUpdated").find("p").text.strip()

    def videos(self, pages: int = 2, videos_concurrency: int = None, pages_concurrency: int = None):
        page_urls = [f"{self.url}?page={page}" for page in range(1, pages + 1)]
        videos_concurrency = videos_concurrency or self.core.config.videos_concurrency
        pages_concurrency = pages_concurrency or self.core.config.pages_concurrency
        yield from self.iterator(page_urls=page_urls, videos_concurrency=videos_concurrency,
                                 pages_concurrency=pages_concurrency,
                                 extractor=extractor_html)


class Pornstar(Helper):
    def __init__(self, url: str, core: BaseCore):
        super(Pornstar, self).__init__(core, video=Video)
        self.url = url
        self.core = core
        self.logger = setup_logger(name="YOUPORN API - [Pornstar]", level=logging.ERROR)
        self.html_content = self.core.fetch(self.url)
        self.soup = BeautifulSoup(self.html_content, parser)

    @cached_property
    def name(self) -> str:
        return self.soup.find("h1", class_="name-title").text.strip()

    @cached_property
    def pornstar_profile_info(self) -> dict:
        profile_info = self.soup.find("ul", class_="profile-info")
        li_tags = profile_info.find_all("li", class_="info-stat")
        dictionary = {}

        for tag in li_tags:
            stuff = tag.find_all("p")
            key = stuff[0].text.strip()
            item = stuff[1].text.strip()
            dictionary.update({key: item})

        return dictionary

    def videos(self, pages: int = 2, videos_concurrency: int = None, pages_concurrency: int = None):
        page_urls = [f"{self.url}?page={page}" for page in range(1, pages + 1)]
        videos_concurrency = videos_concurrency or self.core.config.videos_concurrency
        pages_concurrency = pages_concurrency or self.core.config.pages_concurrency
        yield from self.iterator(page_urls=page_urls, videos_concurrency=videos_concurrency,
                                 pages_concurrency=pages_concurrency,
                                 extractor=extractor_html)

class User:
    def __init__(self, url: str, core: BaseCore):
        self.url = url
        self.core = core
        self.html_content = self.core.fetch(self.url)
        self.soup = BeautifulSoup(self.html_content, parser)

    @cached_property
    def name(self) -> str:
        return self.soup.find("h1", class_="name-title").text.strip()

    @cached_property
    def collections(self) -> Generator[Collection, None, None]:
        container = self.soup.find("ul", class_="playlists_list")
        _collections = container.find_all("li", class_="playlists-container")

        for collection_container in _collections:
            yield Collection(f'https://youporn.com{collection_container.find("a").get("href")}', core=self.core)


class Video:
    def __init__(self, url: str, core: BaseCore):
        self.url = url
        self.core = core
        self.logger = setup_logger(name="YOUPORN API - [Video]", level=logging.ERROR)
        self.html_content = self.core.fetch(self.url)

        if isinstance(self.html_content, Response):
            raise VideoUnavailable(f"The Video: {self.url} is unavailable / not found.")

        if region_locked_pattern.search(self.html_content):
            raise RegionBlocked(f"The Video: {self.url} is not available in your region!")

        self.soup = BeautifulSoup(self.html_content, parser)

    @cached_property
    def title(self) -> str:
        try:
            return self.soup.find("h1", class_="videoTitle tm_videoTitle").text.strip()

        except AttributeError:
            raise f"URL: {self.url} raised an error!"

    @cached_property
    def length(self) -> str:
        return re.search(r'"duration":"(.*?)"', self.html_content).group(1).replace("PT", "").replace("S", "").strip()

    @cached_property
    def rating(self) -> str:
        return self.soup.find("span", class_="tm_rating_percent").text.strip()

    @cached_property
    def views(self) -> str:
        return self.soup.find("span", class_="infoValue tm_infoValue").text.strip()

    @cached_property
    def publish_date(self) -> str:
        return self.soup.find("span", class_="publishedDate").text.strip()

    @cached_property
    def author(self) -> Pornstar | Channel:
        link = f'https://youporn.com{self.soup.find("div", class_="submitByLink").find("a").get("href")}'
        if "channel" in link:
            return Channel(link, core=self.core)

        else:
            return Pornstar(link, core=self.core)

    @cached_property
    def m3u8_base_url(self) -> str:
        media_definitions = re.search(r'mediaDefinition: (.*?) poster:', self.html_content, re.DOTALL | re.IGNORECASE).group(1)
        url = re.search(r'videoUrl":"(.*?)"', media_definitions).group(1).replace('\\', '')

        content = self.core.fetch(url)
        return build_master_playlist(json.loads(content))

    @cached_property
    def thumbnail(self) -> str:
        return re.search(r"poster: '(.*?)'", self.html_content).group(1)

    @cached_property
    def categories(self) -> List[str]:
        categories_ = self.soup.find_all("a", class_="button bubble-button categories-tags tm_carousel_tag js-pop")
        categories = []

        for category in categories_:
            categories.append(category.text)

        return categories

    @cached_property
    def pornstars(self) -> Generator[Pornstar, None, None]:
        pornstars_ = self.soup.find_all("a", class_="button bubble-button tm_carousel_tag")

        for pornstar_object in pornstars_:
            yield Pornstar(f'https://youporn.com{pornstar_object["href"]}', core=self.core)

    def download(self, quality, path="./", callback=None, no_title=False, remux: bool = False,
                 callback_remux=None, start_segment: int = 0, stop_event: Optional[threading.Event] = None,
                 segment_state_path: Optional[str] = None, segment_dir: Optional[str] = None,
                 return_report: bool = False, cleanup_on_stop: bool = True, keep_segment_dir: bool = False
                 ) -> bool:
        """
        :param callback:
        :param quality:
        :param path:
        :param no_title:
        :param remux:
        :param callback_remux:
        :param start_segment:
        :param stop_event:
        :param segment_state_path:
        :param segment_dir:
        :param return_report:
        :param cleanup_on_stop:
        :param keep_segment_dir:
        :return:
        """
        if not no_title:
            path = os.path.join(path, f"{self.title}.mp4")

        return self.core.download(video=self, quality=quality, path=path, callback=callback, remux=remux,
                           callback_remux=callback_remux, start_segment=start_segment, stop_event=stop_event,
                           segment_state_path=segment_state_path, segment_dir=segment_dir, return_report=return_report,
                           cleanup_on_stop=cleanup_on_stop, keep_segment_dir=keep_segment_dir)


    def get_segments(self, quality) -> list:
        """
        :param quality: (str, Quality) The video quality
        :return: (list) A list of segments (the .ts files)
        """
        segments = self.core.get_segments(quality=quality, m3u8_url_master=self.m3u8_base_url)
        return segments


class Client(Helper):
    def __init__(self, core: Optional[BaseCore] = None):
        super().__init__(core, video=Video)
        self.core = core or BaseCore(config=RuntimeConfig())
        self.core.initialize_session()
        self.core.session.headers.update(headers)

    def get_video(self, url: str) -> Video:
        return Video(url, core=self.core)

    def get_pornstar(self, url: str) -> Pornstar:
        return Pornstar(url, core=self.core)

    def get_channel(self, url: str) -> Channel:
        return Channel(url, core=self.core)

    def get_collection(self, url: str) -> Collection:
        return Collection(url, core=self.core)

    def search_videos(self, query: str, pages: int = 0, max_workers=20,
                      filter_relevance: Literal[
                          "views", "rating", "date", "duration"
                      ] = None,
                      filter_duration_minimum: Literal[
                          "10", "20", "30", "40", "50", "60"
                      ] = None,
                      filter_duration_maximum: Literal[
                          "10", "20", "30", "40", "50", "60"
                      ] = None,
                      filter_resolution: Literal[
                          "VR", "HD"
                      ] = None,
                      videos_concurrency: int = None,
                      pages_concurrency: int = None,
                      ):
        # Define basic filters
        res = ""
        min_minutes = ""
        max_minutes = ""
        query = f"query={query}&"
        filter = f"/?"

        if filter_relevance:
            filter = f"/{filter_relevance}/?"

        if filter_resolution:
            res = f"res={filter_resolution}&"

        if filter_duration_minimum:
            min_minutes = f"min_minutes={filter_duration_minimum}&"

        if filter_duration_maximum:
            max_minutes = f"max_minutes={filter_duration_maximum}&"

        videos_concurrency = videos_concurrency or self.core.config.videos_concurrency
        pages_concurrency = pages_concurrency or self.core.config.pages_concurrency

        page_urls = [f"https://youporn.com{filter}{query}{res}{min_minutes}{max_minutes}&page={page}" for page in range(1, pages + 1)]
        yield from self.iterator(page_urls=page_urls, videos_concurrency=videos_concurrency, pages_concurrency=pages_concurrency,
                                 extractor=extractor_html)
