from __future__ import annotations

import asyncio
import re
from typing import TYPE_CHECKING

from aiolimiter import AsyncLimiter
from yarl import URL

from ..base_functions.base_functions import (
    check_direct,
    create_media_item,
    log,
    logger,
    make_title_safe,
)
from ..base_functions.data_classes import DomainItem
from ..base_functions.error_classes import NoExtensionFailure

if TYPE_CHECKING:
    from ..base_functions.base_functions import ErrorFileWriter
    from ..base_functions.sql_helper import SQLHelper
    from ..client.client import ScrapeSession


class ShareXCrawler:
    def __init__(self, *, include_id=False, quiet: bool, SQL_Helper: SQLHelper, error_writer: ErrorFileWriter):
        self.include_id = include_id
        self.quiet = quiet
        self.SQL_Helper = SQL_Helper
        self.limiter = AsyncLimiter(8, 1)
        self.semaphore = asyncio.Semaphore(4)

        self.error_writer = error_writer

    async def fetch(self, session: ScrapeSession, url: URL) -> DomainItem:
        """Director for ShareX scraper"""
        assert url.host is not None
        domain_obj = DomainItem(url.host.lower(), {})

        if await check_direct(url):
            log(f"Starting: {url}", quiet=self.quiet, style="green")
            url = url.with_name(url.name.replace('.md.', '.').replace('.th.', '.'))
            url = await self.jpg_church_from_fish(url)
            media_item = await create_media_item(url, url, self.SQL_Helper, "sharex")
            await domain_obj.add_media("Loose ShareX Files", media_item)
        else:
            async with self.semaphore:
                log(f"Starting: {url}", quiet=self.quiet, style="green")
                if "album" in url.parts or "a" in url.parts:
                    await self.parse(session=session, url=url, domain_obj=domain_obj)
                elif "albums" in url.parts:
                    await self.get_albums(session, url, domain_obj)
                elif 'image' in url.parts or 'img' in url.parts or 'images' in url.parts:
                    await self.get_singular(session, url, domain_obj)
                else:
                    await self.parse_profile(session, url, domain_obj)

        await self.SQL_Helper.insert_domain("sharex", url, domain_obj)
        log(f"Finished: {url}", quiet=self.quiet, style="green")
        return domain_obj

    async def jpg_church_from_fish(self, url: URL) -> URL:
        pattern = r"simp([1-5])\.jpg\.fish/"
        return_url = URL(re.sub(pattern, r'simp\1.jpg.church/', str(url)))
        # The below is a makeshift fix until jpg.church has fully transitioned over to their new caching structure
        # At the time of doing this, 2,4,6 are on the new structure, 1,3,5 are on the old
        # pattern2 = r"simp([1,3,5])\.jpg\.church/"
        # return_url = URL(re.sub(pattern2, r'simp\1.jpg.fish/', str(return_url)))
        return return_url

    async def get_albums(self, session: ScrapeSession, url: URL, domain_obj: DomainItem) -> None:
        """Handles scraping for Albums"""
        try:
            async with self.limiter:
                soup = await session.get_BS4(url)
            albums = soup.select("a[class='image-container --media']")
            for album in albums:
                album_url = URL(album.get('href'))
                await self.parse(session=session, url=album_url, domain_obj=domain_obj)

            next_page = soup.select_one('li.pagination-next a')
            if not next_page:
                next_page = soup.select_one('a[data-pagination=next]')
            if next_page is not None:
                next_page = next_page.get('href')
                if next_page is not None:
                    next_page = URL(next_page)
                    await self.get_albums(session, next_page, domain_obj)

        except Exception as e:
            logger.debug("Error encountered while handling %s", url, exc_info=True)
            await self.error_writer.write_errored_scrape(url, e, self.quiet)

    async def get_singular(self, session: ScrapeSession, url: URL, domain_obj: DomainItem) -> None:
        """Handles scraping for singular files"""
        await asyncio.sleep(1)
        try:
            async with self.limiter:
                soup = await session.get_BS4(url)
            link = URL(soup.select_one("input[id=embed-code-2]").get('value'))
            link = link.with_name(link.name.replace('.md.', '.').replace('.th.', '.'))
            link = await self.jpg_church_from_fish(link)

            media_item = await create_media_item(link, url, self.SQL_Helper, "sharex")
            await domain_obj.add_media("Loose ShareX Files", media_item)
        except Exception as e:
            logger.debug("Error encountered while handling %s", url, exc_info=True)
            await self.error_writer.write_errored_scrape(url, e, self.quiet)

    async def get_sub_album_links(self, session: ScrapeSession, url: URL, og_title: str,
                                  domain_obj: DomainItem) -> None:
        try:
            async with self.limiter:
                soup = await session.get_BS4(url)
            albums = soup.select("div[class=pad-content-listing] div")
            for album in albums:
                album_url = album.get('data-url-short')
                if album_url is not None:
                    album_url = URL(album_url)
                    await self.parse(session=session, url=album_url, og_title=og_title, domain_obj=domain_obj)
        except Exception as e:
            logger.debug("Error encountered while handling %s", url, exc_info=True)
            await self.error_writer.write_errored_scrape(url, e, self.quiet)

    async def parse_profile(self, session: ScrapeSession, url: URL, domain_obj: DomainItem) -> None:
        """Handles scraping for profiles"""
        try:
            async with self.limiter:
                soup = await session.get_BS4(url)
            title = soup.select_one("div[class=header] h1 strong").get_text()
            if title is None:
                title = url.name
            elif self.include_id:
                titlep2 = url.name
                title = title + " - " + titlep2
            title = await make_title_safe(title.replace(r"\n", "").strip())
            await self.get_list_links(session, url, title, domain_obj)

        except Exception as e:
            logger.debug("Error encountered while handling %s", url, exc_info=True)
            await self.error_writer.write_errored_scrape(url, e, self.quiet)

    async def get_list_links(self, session: ScrapeSession, url: URL, title: str, domain_obj: DomainItem) -> None:
        """Gets final links and adds to domain_obj"""
        try:
            async with self.limiter:
                soup = await session.get_BS4(url)
            assert url.host is not None
            if 'jpg.fish' in url.host or 'jpg.church' in url.host or 'jpg.pet' in url.host or 'jpeg.pet' in url.host or "jpg1.su" in url.host or "jpg2.su" in url.host or "jpg3.su" in url.host:
                links = soup.select("a[href*=img] img")
            else:
                links = soup.select("a[href*=image] img")
            for link in links:
                link = URL(link.get('src'))
                link = link.with_name(link.name.replace('.md.', '.').replace('.th.', '.'))
                link = await self.jpg_church_from_fish(link)

                try:
                    media_item = await create_media_item(link, url, self.SQL_Helper, "sharex")
                except NoExtensionFailure:
                    logger.debug("Couldn't get extension for %s", link)
                    continue
                await domain_obj.add_media(title, media_item)

            next_page = soup.select_one('li.pagination-next a')
            if not next_page:
                next_page = soup.select_one('a[data-pagination=next]')
            if next_page is not None:
                next_page = next_page.get('href')
                if next_page is not None:
                    next_page = URL(next_page)
                    await self.get_list_links(session, next_page, title, domain_obj)
        except Exception as e:
            logger.debug("Error encountered while handling %s", url, exc_info=True)
            await self.error_writer.write_errored_scrape(url, e, self.quiet)

    async def parse(self, *, session: ScrapeSession, url: URL, og_title=None, domain_obj: DomainItem) -> None:
        try:
            async with self.limiter:
                soup = await session.get_BS4(url)

            title = soup.select_one("a[data-text=album-name]").get_text()
            if title is None:
                title = url.name
            elif self.include_id:
                titlep2 = url.name
                title = title + " - " + titlep2
            title = await make_title_safe(title.replace(r"\n", "").strip())

            if og_title is not None:
                title = og_title + "/" + title

            try:
                sub_albums = URL(soup.select_one("a[id=tab-sub-link]").get("href"))
                await self.get_sub_album_links(session, sub_albums, title, domain_obj)
            finally:
                list_recent = URL(soup.select_one("a[id=list-most-recent-link]").get("href"))
                await self.get_list_links(session, list_recent, title, domain_obj)

        except Exception as e:
            logger.debug("Error encountered while handling %s", url, exc_info=True)
            await self.error_writer.write_errored_scrape(url, e, self.quiet)
