import random
import time
import typing
import urllib.parse

import bs4

import chitragupt.abc.browser as browser_abc
import chitragupt.exceptions as exceptions
import chitragupt.modules.browsers as base_browser


class ThirteenTabs(base_browser.BrowserBase):
    """
    A class to interact with the ThirteenTabs search engine.

    This class is used to search for web pages, images, and videos on ThirteenTabs.

    Attributes
    ----------
    session : curl_cffi.requests.Session
        The session object used to make HTTP requests.
    initialized : bool
        Flag indicating whether the browser has been initialized.
    """

    @staticmethod
    def get_useragent() -> str:
        """
        Generate a random user agent string.

        Returns
        -------
        str
            A randomly generated user agent string.
        """
        return (
            f"Lynx/{random.randint(2, 3)}.{random.randint(8, 9)}.{random.randint(0, 2)} "
            f"libwww-FM/{random.randint(2, 3)}.{random.randint(13, 15)} "
            f"SSL-MM/{random.randint(1, 2)}.{random.randint(3, 5)} "
            f"OpenSSL/{random.randint(1, 3)}.{random.randint(0, 4)}.{random.randint(0, 9)}"
        )

    def initialize(
        self,
        verify: bool = True,
        headers: typing.Optional[typing.Dict[str, str]] = None,
        proxy: typing.Optional[str] = None,
        proxies: typing.Optional[
            typing.Union[typing.Dict[str, str], str]
        ] = None,
        timeout: float = 10.0,
    ) -> None:
        """
        Initialize the ThirteenTabs browser with the given parameters.

        Parameters
        ----------
        verify : bool, optional
            Whether to verify TLS certificates.
        headers : Optional[Dict[str, str]], optional
            Custom headers to use; defaults to a standard Chrome user agent header.
        proxy : Optional[str], optional
            A single proxy URL.
        proxies : Optional[Union[Dict[str, str], str]], optional
            A dictionary of proxies or a single proxy URL.
        timeout : float, optional
            Request timeout in seconds.
        """
        # Set impersonation using BrowserType from the base_browser module.
        self.session.impersonate = base_browser.BrowserType.chrome120  # type: ignore
        self.session.verify = verify
        self.session.headers = (  # type: ignore
            headers
            or {  # type: ignore
                "User-Agent": (
                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                    "AppleWebKit/537.36 (KHTML, like Gecko) "
                    "Chrome/120.0.0.0 Safari/537.36"
                )
            }
        )
        self.session.proxies = proxies or proxy  # type: ignore
        self.session.timeout = timeout
        self.session.base_url = "https://www.13tabs.com"
        self.session.allow_redirects = True
        self.initialized = True

    def _get_results(
        self,
        term: str,
        results: int = 10,
        lang: str = "en",
        start: int = 0,
        safe: bool = False,
        region: typing.Optional[str] = None,
    ) -> str:
        """
        Make a GET request to the ThirteenTabs search endpoint.

        Parameters
        ----------
        term : str
            The search term.
        results : int, optional
            Number of results to request (default is 10).
        lang : str, optional
            Language code (default is "en").
        start : int, optional
            Index of the first result (default is 0).
        safe : bool, optional
            Whether to enable safe search (default is False).
        region : Optional[str], optional
            Region code (default is None).

        Returns
        -------
        str
            The HTML content from the search endpoint.
        """
        assert self.initialized, (
            "Browser must be initialized before making a request."
        )
        response = self.session.get(
            url="https://www.13tabs.com/search",
            headers={
                "User-Agent": self.get_useragent(),
                "Accept": "*/*",
            },
            params={
                "q": term,
                "p": "web",
                "so": "tt",
                "num": results + 2,
                "hl": lang,
                "start": start,
                "safe": "active" if safe else "off",
                "gl": region,
            },
        )
        response.raise_for_status()
        return response.text

    def search(
        self,
        query: str,
        counter: int = 10,
    ) -> typing.List[browser_abc.ThirteenTabsSearchResult]:
        """
        Searches ThirteenTabs for a query and returns a list of search results,
        including additional fields such as favicon and source if available.
        """
        if not self.initialized:
            raise exceptions.ChitraGuptBrowserNotInitializedError(
                f"Browser not initialized. Call {self.__class__.__name__}.initialize() first."
            )
        fetched_links: typing.Set[str] = set()
        start: int = 0
        fetched_results: int = 0
        results: typing.List[browser_abc.ThirteenTabsSearchResult] = []

        while fetched_results < counter:
            html_content = self._get_results(
                term=query,
                results=counter - fetched_results,
                lang="en",
                start=start,
                safe=False,
                region=None,
            )
            soup = bs4.BeautifulSoup(html_content, "html.parser")
            result_blocks = soup.find_all("div", class_="ezO2md")

            new_results: int = 0
            for result in result_blocks:
                link_tag = result.find("a", href=True)
                title_tag = (
                    link_tag.find("span", class_="CVA68e")
                    if link_tag
                    else None
                )
                description_tag = result.find("span", class_="FrIlee")
                # Extract additional fields if available:
                favicon_tag = result.find(
                    "img", class_="resultUrlIcon", src=True
                )
                source_tag = result.find("span", class_="webdn")
                if link_tag and title_tag and description_tag:
                    link = urllib.parse.unquote(
                        link_tag["href"].split("&")[0].replace("/url?q=", "")
                    )
                    if link in fetched_links:
                        continue
                    fetched_links.add(link)
                    title = title_tag.get_text().strip()
                    description = description_tag.get_text().strip()
                    # Extract favicon and source if found.
                    favicon: typing.Union[str, typing.List[str]] = ""
                    if favicon_tag and isinstance(favicon_tag, bs4.Tag):
                        favicon: typing.Union[str, typing.List[str]] = (
                            favicon_tag.get("src", "")
                        )
                    source: str = ""
                    if source_tag:
                        source = source_tag.get_text().strip().split()[0]
                    fetched_results += 1
                    new_results += 1
                    results.append(
                        browser_abc.ThirteenTabsSearchResult(
                            link=link,
                            title=title,
                            description=description,
                            favicon=favicon,
                            source=source,
                        )
                    )
                    if fetched_results >= counter:
                        return results
            if new_results == 0:
                break
            start += 10
            time.sleep(0.0)
        return results

    def image(
        self,
        query: str,
        counter: int = 10,
    ) -> typing.List[browser_abc.ThirteenTabsImageResult]:
        """
        Search for images on ThirteenTabs.

        Parameters
        ----------
        query : str
            The search query.
        counter : int, optional
            The number of results to return (default is 10).

        Returns
        -------
        List[ThirteenTabsImageResult]
            A list of image search results.

        Raises
        ------
        ChitraGuptBrowserNotInitializedError
            If the browser is not initialized.
        ValueError
            If no image container or images are found.
        """
        if not self.initialized:
            raise exceptions.ChitraGuptBrowserNotInitializedError(
                f"Browser not initialized. Call {self.__class__.__name__}.initialize() first."
            )
        resp = self.session.get(f"search/?q={query}&p=images&so=tt")
        soup = bs4.BeautifulSoup(resp.content, "html.parser")
        image_div = soup.find("div", id="images")
        if not image_div:
            raise ValueError("No image container found.")
        if not isinstance(image_div, bs4.Tag):
            raise ValueError("Invalid image container.")
        images = image_div.find_all("img")
        if not images:
            raise ValueError("No images found on the page.")
        image_result: typing.List[browser_abc.ThirteenTabsImageResult] = []
        for index, img in enumerate(images):
            if index >= counter:
                break
            if not isinstance(img, bs4.Tag):
                continue
            onclick_attr = img.get("onclick", "")
            preview_url: typing.Optional[str] = None
            if "prevw(" in onclick_attr:
                parts = str(onclick_attr).split("'")
                if len(parts) > 1:
                    preview_url = parts[1]
            image_url = img.get("src")
            if image_url and isinstance(image_url, str):
                image_result.append(
                    browser_abc.ThirteenTabsImageResult(
                        image_url=image_url,
                        preview_url=preview_url,
                        class_names=img.get("class", []),  # type: ignore
                        height=img.get("height"),  # type: ignore
                        alt_text=img.get("alt", ""),  # type: ignore
                    )
                )
        return image_result

    def video(
        self,
        query: str,
        counter: int = 10,
    ) -> typing.List[browser_abc.ThirteenTabsVideoResult]:
        """
        Search for videos on ThirteenTabs.

        Parameters
        ----------
        query : str
            The search query.
        counter : int, optional
            The number of results to return (default is 10).

        Returns
        -------
        List[ThirteenTabsVideoResult]
            A list of video search results.

        Raises
        ------
        ChitraGuptBrowserNotInitializedError
            If the browser is not initialized.
        """
        if not self.initialized:
            raise exceptions.ChitraGuptBrowserNotInitializedError(
                f"Browser not initialized. Call {self.__class__.__name__}.initialize() first."
            )
        resp = self.session.get(f"search/?q={query}&p=videos")
        soup = bs4.BeautifulSoup(resp.content, "html.parser")
        video_list: typing.Sequence[bs4.element.Tag] = soup.find_all(
            "div", class_="videoList"
        )
        videos: typing.List[browser_abc.ThirteenTabsVideoResult] = []
        for index, video in enumerate(video_list):
            if index >= counter:
                break
            if not isinstance(video, bs4.Tag):
                continue
            title_tag = video.find("a", class_="yt")
            url_tag = video.find("a")
            img_tag = video.find("img", class_="vdoimg")
            channel_tag = video.find("span", class_="channel")
            if (
                title_tag
                and isinstance(url_tag, bs4.Tag)
                and isinstance(img_tag, bs4.Tag)
                and channel_tag
            ):
                title: str = title_tag.get_text().strip()
                url: typing.Any = url_tag.get("href")
                image_url: typing.Any = img_tag.get("href")
                channel: str = channel_tag.get_text().strip()
                videos.append(
                    browser_abc.ThirteenTabsVideoResult(
                        title=title,
                        url=url,
                        image_url=image_url,
                        channel=channel,
                    )
                )
        return videos
