import time
import logging
from abc import ABC, abstractmethod
from typing import Optional, List, Dict, Any
from datetime import datetime
from urllib.parse import urljoin
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

logger = logging.getLogger(__name__)


class BaseScraper(ABC):
    def __init__(
        self,
        base_url: str,
        rate_limit_seconds: float = 2.0,
        max_retries: int = 3,
        timeout: int = 30
    ):
        self.base_url = base_url.rstrip("/")
        self.rate_limit_seconds = rate_limit_seconds
        self.max_retries = max_retries
        self.timeout = timeout
        self.last_request_time = 0.0
        self.session = self._create_session()

    def _create_session(self) -> requests.Session:
        session = requests.Session()

        retry_strategy = Retry(
            total=self.max_retries,
            backoff_factor=1.0,
            status_forcelist=[429, 500, 502, 503, 504],
            allowed_methods=["HEAD", "GET", "OPTIONS"]
        )

        adapter = HTTPAdapter(max_retries=retry_strategy, pool_maxsize=10)
        session.mount("http://", adapter)
        session.mount("https://", adapter)

        session.headers.update({
            "User-Agent": "Mozilla/5.0 (compatible; MealPlannerBot/1.0; +https://mealplanner.local)",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "Accept-Language": "en-US,en;q=0.5",
        })

        return session

    def _rate_limit(self) -> None:
        elapsed = time.time() - self.last_request_time
        if elapsed < self.rate_limit_seconds:
            sleep_time = self.rate_limit_seconds - elapsed
            logger.debug(f"Rate limiting: sleeping {sleep_time:.2f}s")
            time.sleep(sleep_time)
        self.last_request_time = time.time()

    def _get(self, url: str, **kwargs) -> Optional[requests.Response]:
        self._rate_limit()

        full_url = url if url.startswith("http") else urljoin(self.base_url, url)

        for attempt in range(self.max_retries + 1):
            try:
                logger.debug(f"GET {full_url} (attempt {attempt + 1})")
                response = self.session.get(full_url, timeout=self.timeout, **kwargs)
                response.raise_for_status()
                return response

            except requests.exceptions.RequestException as e:
                logger.warning(f"Request failed (attempt {attempt + 1}): {e}")
                if attempt >= self.max_retries:
                    logger.error(f"Max retries reached for {full_url}")
                    return None
                time.sleep(2 ** attempt)

        return None

    def _post(self, url: str, data: Dict[str, Any] = None, **kwargs) -> Optional[requests.Response]:
        self._rate_limit()

        full_url = url if url.startswith("http") else urljoin(self.base_url, url)

        for attempt in range(self.max_retries + 1):
            try:
                logger.debug(f"POST {full_url} (attempt {attempt + 1})")
                response = self.session.post(full_url, data=data, timeout=self.timeout, **kwargs)
                response.raise_for_status()
                return response

            except requests.exceptions.RequestException as e:
                logger.warning(f"POST request failed (attempt {attempt + 1}): {e}")
                if attempt >= self.max_retries:
                    logger.error(f"Max retries reached for {full_url}")
                    return None
                time.sleep(2 ** attempt)

        return None

    @abstractmethod
    def scrape(self) -> Dict[str, Any]:
        pass

    def log_scrape(self, status: str, items_scraped: int = 0, error_message: str = None) -> None:
        logger.info(f"Scrape {status}: {items_scraped} items, error: {error_message}")


class SeleniumScraper(BaseScraper):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._playwright = None

    def _init_playwright(self):
        if self._playwright is None:
            from playwright.sync_api import sync_playwright
            self._playwright = sync_playwright().start()
            self._browser = self._playwright.chromium.launch(headless=True)
            self._context = self._browser.new_context(
                user_agent="Mozilla/5.0 (compatible; MealPlannerBot/1.0)"
            )

    def _get_playwright(self):
        self._init_playwright()
        return self._playwright

    def get_browser_page(self, url: str):
        self._get_playwright()
        page = self._context.new_page()
        page.goto(url, wait_until="networkidle", timeout=30000)
        return page

    def cleanup(self):
        if self._browser:
            self._browser.close()
        if self._playwright:
            self._playwright.stop()
            self._playwright = None