import asyncio
from fsm_crawl.browser.engine.consent.playwright_consent_manager import PlaywrightConsentManager
from fsm_crawl.browser.engine.generic_browser_engine import GenericBrowserEngine
import subprocess
from playwright.async_api import async_playwright, Page, Browser
from fsm_crawl.utils.domain import fqdn_from_url, normalize_url, is_valid_http_url
from urllib.parse import urljoin
from playwright.async_api import Error as PlaywrightError


class PlaywrightEngine(GenericBrowserEngine):
    def __init__(self, headless=True, num_tabs=10):
        self.headless = headless
        self.num_tabs = num_tabs
        self.browser: Browser | None = None
        self.pages: list[Page] = []
        self.current_page_index = 0
        self.pw = None
        self.consent = PlaywrightConsentManager()
        self._consent_tasks: list[asyncio.Task] = []
        self._stop_consent = False
        
    # consent
    async def _consent_loop(self, page: Page, interval: float = 0.5):
        """
        Background loop that tries to accept cookies repeatedly on a single page.
        Runs until `_stop_consent` is set.
        """
        while not self._stop_consent:
            try:
                await self.consent.try_accept(page)
            except Exception:
                pass
            await asyncio.sleep(interval)

    async def start_consent_task(self):
        """Start background cookie acceptor tasks for all pages."""
        self._stop_consent = False
        self._consent_tasks = [
            asyncio.create_task(self._consent_loop(page)) 
            for page in self.pages
        ]

    async def stop_consent_task(self):
        """Stop all background cookie acceptor tasks."""
        if self._consent_tasks:
            self._stop_consent = True
            await asyncio.gather(*self._consent_tasks, return_exceptions=True)
            self._consent_tasks = []
        
    # lifecycle
    def install(self):
        """Install Playwright browsers. Works both in development and pip installations."""
        try:
            # Try direct playwright install (works with pip installs)
            subprocess.run(["playwright", "install", "chromium"], check=True)
        except (FileNotFoundError, subprocess.CalledProcessError):
            try:
                # Fallback to poetry run (for development)
                subprocess.run(["poetry", "run", "playwright", "install", "chromium"], check=True)
            except Exception:
                # Silent fail - browsers might already be installed
                pass

    async def start(self):
        self.pw = await async_playwright().start()
        self.browser = await self.pw.chromium.launch(headless=self.headless)
        # Create multiple pages/tabs
        self.pages = [await self.browser.new_page() for _ in range(self.num_tabs)]
        self.current_page_index = 0
        return True
        
    def is_ready(self) -> bool:
        return self.browser is not None and len(self.pages) > 0

    async def stop(self):
        # Close all pages
        for page in self.pages:
            try:
                await page.close()
            except Exception:
                pass
        await self.browser.close()
        await self.pw.stop()
    
    def _get_next_page(self) -> Page:
        """Round-robin selection of the next available page."""
        page = self.pages[self.current_page_index]
        self.current_page_index = (self.current_page_index + 1) % len(self.pages)
        return page
    
    # page control
    async def perform_scroll(self, amounts, scroll_delay=0.2, page: Page = None):
        """Perform scrolling on a specific page."""
        if page is None:
            page = self.pages[0]
        for amount in amounts:
            await page.mouse.wheel(0, amount)
            await asyncio.sleep(scroll_delay)

    async def goto(self, url, page: Page = None) -> list[str]:
        """
        Navigate to a URL on a page, accept cookies, and return same-domain links.
        """
        if page is None:
            page = self._get_next_page()
            
        try:
            await page.goto(
                normalize_url(url),
                wait_until="domcontentloaded",
                timeout=15000
            )
        except PlaywrightError as e:
            # print(f"[Goto] Failed to load {url}: {e}")
            return []

        page_fqdn = fqdn_from_url(url)
        if not page_fqdn:
            return []

        # Get all links
        try:
            raw_links = await page.eval_on_selector_all(
                "a",
                "els => els.map(e => e.getAttribute('href')).filter(h => h)"
            )
        except PlaywrightError:
            return []

        # Convert to absolute URLs
        absolute_links = [urljoin(url, link) for link in raw_links]

        same_domain_links = [
            link for link in absolute_links
            if fqdn_from_url(link) == page_fqdn and is_valid_http_url(link)
        ]

        return same_domain_links
    
    # networking
    def on_response(self, handler):
        """Attach response handler to all pages."""
        for page in self.pages:
            page.on("response", handler)
        
    async def on_request(self, handler):
        """Attach request handler to all pages."""
        for page in self.pages:
            await page.route("**/*", handler)
        

