import random
from typing import Optional, Literal, List, Dict, Tuple
import re


class UserAgentGenerator:
    """
    Generate random user agents with specified constraints.

    Attributes:
        desktop_platforms (dict): A dictionary of possible desktop platforms and their corresponding user agent strings.
        mobile_platforms (dict): A dictionary of possible mobile platforms and their corresponding user agent strings.
        browser_combinations (dict): A dictionary of possible browser combinations and their corresponding user agent strings.
        rendering_engines (dict): A dictionary of possible rendering engines and their corresponding user agent strings.
        chrome_versions (list): A list of possible Chrome browser versions.
        firefox_versions (list): A list of possible Firefox browser versions.
        edge_versions (list): A list of possible Edge browser versions.
        safari_versions (list): A list of possible Safari browser versions.
        ios_versions (list): A list of possible iOS browser versions.
        android_versions (list): A list of possible Android browser versions.

        Methods:
            generate_user_agent(
                platform: Literal["desktop", "mobile"] = "desktop",
                browser: str = "chrome",
                rendering_engine: str = "chrome_webkit",
                chrome_version: Optional[str] = None,
                firefox_version: Optional[str] = None,
                edge_version: Optional[str] = None,
                safari_version: Optional[str] = None,
                ios_version: Optional[str] = None,
                android_version: Optional[str] = None
            ): Generates a random user agent string based on the specified parameters.
    """

    def __init__(self):
        # Previous platform definitions remain the same...
        self.desktop_platforms = {
            "windows": {
                "10_64": "(Windows NT 10.0; Win64; x64)",
                "10_32": "(Windows NT 10.0; WOW64)",
            },
            "macos": {
                "intel": "(Macintosh; Intel Mac OS X 10_15_7)",
                "newer": "(Macintosh; Intel Mac OS X 10.15; rv:109.0)",
            },
            "linux": {
                "generic": "(X11; Linux x86_64)",
                "ubuntu": "(X11; Ubuntu; Linux x86_64)",
                "chrome_os": "(X11; CrOS x86_64 14541.0.0)",
            },
        }

        self.mobile_platforms = {
            "android": {
                "samsung": "(Linux; Android 13; SM-S901B)",
                "pixel": "(Linux; Android 12; Pixel 6)",
                "oneplus": "(Linux; Android 13; OnePlus 9 Pro)",
                "xiaomi": "(Linux; Android 12; M2102J20SG)",
            },
            "ios": {
                "iphone": "(iPhone; CPU iPhone OS 16_5 like Mac OS X)",
                "ipad": "(iPad; CPU OS 16_5 like Mac OS X)",
            },
        }

        # Browser Combinations
        self.browser_combinations = {
            1: [["chrome"], ["firefox"], ["safari"], ["edge"]],
            2: [["gecko", "firefox"], ["chrome", "safari"], ["webkit", "safari"]],
            3: [["chrome", "safari", "edge"], ["webkit", "chrome", "safari"]],
        }

        # Rendering Engines with versions
        self.rendering_engines = {
            "chrome_webkit": "AppleWebKit/537.36",
            "safari_webkit": "AppleWebKit/605.1.15",
            "gecko": [  # Added Gecko versions
                "Gecko/20100101",
                "Gecko/20100101",  # Firefox usually uses this constant version
                "Gecko/2010010",
            ],
        }

        # Browser Versions
        self.chrome_versions = [
            "Chrome/119.0.6045.199",
            "Chrome/118.0.5993.117",
            "Chrome/117.0.5938.149",
            "Chrome/116.0.5845.187",
            "Chrome/115.0.5790.171",
        ]

        self.edge_versions = [
            "Edg/119.0.2151.97",
            "Edg/118.0.2088.76",
            "Edg/117.0.2045.47",
            "Edg/116.0.1938.81",
            "Edg/115.0.1901.203",
        ]

        self.safari_versions = [
            "Safari/537.36",  # For Chrome-based
            "Safari/605.1.15",
            "Safari/604.1",
            "Safari/602.1",
            "Safari/601.5.17",
        ]

        # Added Firefox versions
        self.firefox_versions = [
            "Firefox/119.0",
            "Firefox/118.0.2",
            "Firefox/117.0.1",
            "Firefox/116.0",
            "Firefox/115.0.3",
            "Firefox/114.0.2",
            "Firefox/113.0.1",
            "Firefox/112.0",
            "Firefox/111.0.1",
            "Firefox/110.0",
        ]

    def get_browser_stack(self, num_browsers: int = 1) -> List[str]:
        """
        Get a valid combination of browser versions.

        How it works:
        1. Check if the number of browsers is supported.
        2. Randomly choose a combination of browsers.
        3. Iterate through the combination and add browser versions.
        4. Return the browser stack.

        Args:
            num_browsers: Number of browser specifications (1-3)

        Returns:
            List[str]: A list of browser versions.
        """
        if num_browsers not in self.browser_combinations:
            raise ValueError(f"Unsupported number of browsers: {num_browsers}")

        combination = random.choice(self.browser_combinations[num_browsers])
        browser_stack = []

        for browser in combination:
            if browser == "chrome":
                browser_stack.append(random.choice(self.chrome_versions))
            elif browser == "firefox":
                browser_stack.append(random.choice(self.firefox_versions))
            elif browser == "safari":
                browser_stack.append(random.choice(self.safari_versions))
            elif browser == "edge":
                browser_stack.append(random.choice(self.edge_versions))
            elif browser == "gecko":
                browser_stack.append(random.choice(self.rendering_engines["gecko"]))
            elif browser == "webkit":
                browser_stack.append(self.rendering_engines["chrome_webkit"])

        return browser_stack

    def generate(
        self,
        device_type: Optional[Literal["desktop", "mobile"]] = None,
        os_type: Optional[str] = None,
        device_brand: Optional[str] = None,
        browser_type: Optional[Literal["chrome", "edge", "safari", "firefox"]] = None,
        num_browsers: int = 3,
    ) -> str:
        """
        Generate a random user agent with specified constraints.

        Args:
            device_type: 'desktop' or 'mobile'
            os_type: 'windows', 'macos', 'linux', 'android', 'ios'
            device_brand: Specific device brand
            browser_type: 'chrome', 'edge', 'safari', or 'firefox'
            num_browsers: Number of browser specifications (1-3)
        """
        # Get platform string
        platform = self.get_random_platform(device_type, os_type, device_brand)

        # Start with Mozilla
        components = ["Mozilla/5.0", platform]

        # Add browser stack
        browser_stack = self.get_browser_stack(num_browsers)

        # Add appropriate legacy token based on browser stack
        if "Firefox" in str(browser_stack):
            components.append(random.choice(self.rendering_engines["gecko"]))
        elif "Chrome" in str(browser_stack) or "Safari" in str(browser_stack):
            components.append(self.rendering_engines["chrome_webkit"])
            components.append("(KHTML, like Gecko)")

        # Add browser versions
        components.extend(browser_stack)

        return " ".join(components)

    def generate_with_client_hints(self, **kwargs) -> Tuple[str, str]:
        """Generate both user agent and matching client hints"""
        user_agent = self.generate(**kwargs)
        client_hints = self.generate_client_hints(user_agent)
        return user_agent, client_hints

    def get_random_platform(self, device_type, os_type, device_brand):
        """Helper method to get random platform based on constraints"""
        platforms = (
            self.desktop_platforms
            if device_type == "desktop"
            else self.mobile_platforms
            if device_type == "mobile"
            else {**self.desktop_platforms, **self.mobile_platforms}
        )

        if os_type:
            for platform_group in [self.desktop_platforms, self.mobile_platforms]:
                if os_type in platform_group:
                    platforms = {os_type: platform_group[os_type]}
                    break

        os_key = random.choice(list(platforms.keys()))
        if device_brand and device_brand in platforms[os_key]:
            return platforms[os_key][device_brand]
        return random.choice(list(platforms[os_key].values()))

    def parse_user_agent(self, user_agent: str) -> Dict[str, str]:
        """Parse a user agent string to extract browser and version information"""
        browsers = {
            "chrome": r"Chrome/(\d+)",
            "edge": r"Edg/(\d+)",
            "safari": r"Version/(\d+)",
            "firefox": r"Firefox/(\d+)",
        }

        result = {}
        for browser, pattern in browsers.items():
            match = re.search(pattern, user_agent)
            if match:
                result[browser] = match.group(1)

        return result

    def generate_client_hints(self, user_agent: str) -> str:
        """Generate Sec-CH-UA header value based on user agent string"""
        browsers = self.parse_user_agent(user_agent)

        # Client hints components
        hints = []

        # Handle different browser combinations
        if "chrome" in browsers:
            hints.append(f'"Chromium";v="{browsers["chrome"]}"')
            hints.append('"Not_A Brand";v="8"')

            if "edge" in browsers:
                hints.append(f'"Microsoft Edge";v="{browsers["edge"]}"')
            else:
                hints.append(f'"Google Chrome";v="{browsers["chrome"]}"')

        elif "firefox" in browsers:
            # Firefox doesn't typically send Sec-CH-UA
            return '""'

        elif "safari" in browsers:
            # Safari's format for client hints
            hints.append(f'"Safari";v="{browsers["safari"]}"')
            hints.append('"Not_A Brand";v="8"')

        return ", ".join(hints)


# Example usage:
if __name__ == "__main__":
    generator = UserAgentGenerator()
    print(generator.generate())

    print("\nSingle browser (Chrome):")
    print(generator.generate(num_browsers=1, browser_type="chrome"))

    print("\nTwo browsers (Gecko/Firefox):")
    print(generator.generate(num_browsers=2))

    print("\nThree browsers (Chrome/Safari/Edge):")
    print(generator.generate(num_browsers=3))

    print("\nFirefox on Linux:")
    print(
        generator.generate(
            device_type="desktop",
            os_type="linux",
            browser_type="firefox",
            num_browsers=2,
        )
    )

    print("\nChrome/Safari/Edge on Windows:")
    print(generator.generate(device_type="desktop", os_type="windows", num_browsers=3))
