#!/usr/bin/env python3
"""
Zoom Page Primitive Operation

This module provides page zooming functionality with human-like mouse movement.
Simulates natural zoom behavior using Ctrl+scroll wheel.
"""

import asyncio
import random
import math
from typing import Dict, Any, Tuple, List
from patchright.async_api import Page

from lib.page_management import ManagedPage


class ZoomPage:
    """
    Zoom Page with Human-like Mouse Movement

    Implements realistic page zoom behavior:
    - Human-like mouse movement to zoom coordinates
    - Natural Ctrl+scroll wheel zoom
    - Realistic timing delays

    Usage:
        zoomer = ZoomPage()
        result = await zoomer.zoom(managed_page, x=500, y=300, zoom_level=3)
        reset_result = await zoomer.reset(managed_page)
    """

    def __init__(self):
        """Initialize the zoom page handler."""
        print("🔍 ZoomPage initialized")

    async def zoom(self, managed_page: ManagedPage, x: float, y: float, zoom_level: int = 1, slowmo: int = 100) -> Dict[str, Any]:
        """
        Zoom the page at specified coordinates.

        Args:
            managed_page (ManagedPage): Managed page to zoom
            x (float): X coordinate for zoom center
            y (float): Y coordinate for zoom center
            zoom_level (int): Number of zoom ticks (default: 1)
            slowmo (int): Base delay in milliseconds for timing (default: 100)

        Returns:
            Dict containing zoom result and information
        """
        try:
            print(f"🔍 Zooming at ({x}, {y}) with level {zoom_level}")

            page = managed_page.page

            await page.bring_to_front()
            # Move mouse with human-like curved path
            await self._move_mouse_human_like(managed_page, x, y, slowmo)
            await asyncio.sleep(slowmo / 1000 * random.uniform(0.5, 1.0))

            # Hold Ctrl and scroll wheel to zoom
            await page.keyboard.down('Control')
            await asyncio.sleep(random.uniform(0.05, 0.15))

            await self._zoom_at_mouse(page, x, y, 1.25 if zoom_level > 0 else 0.8)


            # Release Ctrl
            await page.keyboard.up('Control')
            await asyncio.sleep(slowmo / 1000 * random.uniform(0.5, 1.0))

            # Update last known mouse position
            managed_page.last_known_mouse_x = x
            managed_page.last_known_mouse_y = y

            result = {
                "success": True,
                "zoom_level": zoom_level,
                "position": {"x": x, "y": y},
                "url": page.url
            }

            print(f"✅ Page zoomed: level {zoom_level}")
            return result

        except Exception as e:
            error_result = {
                "success": False,
                "zoom_level": zoom_level,
                "error": str(e)
            }
            print(f"❌ Page zoom failed: {e}")
            return error_result

    async def _zoom_at_mouse(self, page, mouse_x, mouse_y, zoom_factor):
        """Zoom the page around a specific point using CSS transforms"""
        await page.evaluate(f"""
            ({{x, y, zoom}}) => {{
                const body = document.body;
                const html = document.documentElement;
                
                // Get current transform
                const currentTransform = body.style.transform || 'scale(1)';
                const currentScale = parseFloat(currentTransform.match(/scale\\(([^)]+)\\)/)?.[1] || 1);
                
                // Calculate new scale
                const newScale = currentScale * zoom;
                
                // Calculate transform origin to zoom around mouse
                const originX = (x / window.innerWidth) * 100;
                const originY = (y / window.innerHeight) * 100;
                
                // Apply transform
                body.style.transformOrigin = `${{originX}}% ${{originY}}%`;
                body.style.transform = `scale(${{newScale}})`;
                html.style.transformOrigin = `${{originX}}% ${{originY}}%`;
                html.style.transform = `scale(${{newScale}})`;
            }}
        """, {"x": mouse_x, "y": mouse_y, "zoom": zoom_factor})

    async def _set_zoom(self, page, zoom_level):
        # Create CDP session
        cdp = await page.context.new_cdp_session(page)
        
        # The send method signature in Python Playwright:
        # send(method, params=None)
        await cdp.send('Emulation.setPageScaleFactor', {
            'pageScaleFactor': zoom_level
        })

    async def reset(self, managed_page: ManagedPage, slowmo: int = 100) -> Dict[str, Any]:
        """
        Reset page zoom to default level (100%).

        Args:
            managed_page (ManagedPage): Managed page to reset zoom
            slowmo (int): Base delay in milliseconds for timing (default: 100)

        Returns:
            Dict containing reset result and information
        """
        try:
            print("🔍 Resetting page zoom")

            page = managed_page.page

            # Hold Ctrl and press 0 to reset zoom
            await page.keyboard.down('Control')
            await asyncio.sleep(random.uniform(0.05, 0.15))

            await self._reset_zoom(page)
            await asyncio.sleep(random.uniform(0.1, 0.2))

            # Release Ctrl
            await page.keyboard.up('Control')
            await asyncio.sleep(slowmo / 1000 * random.uniform(0.5, 1.0))

            result = {
                "success": True,
                "url": page.url
            }

            print(f"✅ Page zoom reset")
            return result

        except Exception as e:
            error_result = {
                "success": False,
                "error": str(e)
            }
            print(f"❌ Page zoom reset failed: {e}")
            return error_result

    async def _reset_zoom(self, page):
        # Create CDP session
        cdp = await page.context.new_cdp_session(page)
        
        # Reset zoom to 1.0
        await cdp.send('Emulation.setPageScaleFactor', {
            'pageScaleFactor': 1.0
        })

    async def _move_mouse_human_like(self, managed_page: ManagedPage, target_x: float, target_y: float, slowmo: int):
        """
        Move mouse from current position to target with human-like curved path.

        Uses cubic Bezier curves with random control points to create natural-looking
        curved movements. Adds jitter and variable speed for realism.

        Args:
            managed_page (ManagedPage): Managed page object
            target_x (float): Target X coordinate
            target_y (float): Target Y coordinate
            slowmo (int): Base delay for timing
        """

        # Starting from current known position
        start_x = managed_page.last_known_mouse_x  
        start_y = managed_page.last_known_mouse_y  

        # Initial random mouse movements (micro adjustments)
        await managed_page.page.mouse.move(start_x, start_y, steps=1)
        await asyncio.sleep(random.uniform(0.01, 0.05))

        # Generate curved path using Bezier curve
        path_points = self._generate_bezier_path(start_x, start_y, target_x, target_y)

        # Move along the path with variable speed and jitter
        total_points = len(path_points)
        for i, (x, y) in enumerate(path_points):
            # Add jitter (small random deviations)
            jitter_x = random.uniform(-1.5, 1.5)
            jitter_y = random.uniform(-1.5, 1.5)

            final_x = x + jitter_x
            final_y = y + jitter_y

            # Move to point
            await managed_page.page.mouse.move(final_x, final_y, steps=1)

            # Variable speed: slower at start/end, faster in middle
            progress = i / total_points
            if progress < 0.2 or progress > 0.8:
                # Slower at start and end
                delay = slowmo / 1000 / total_points * random.uniform(1, 2)
            else:
                # Faster in middle
                delay = slowmo / 1000 / total_points * random.uniform(0.2, 1.0)

            await asyncio.sleep(delay)

    def _generate_bezier_path(self, start_x: float, start_y: float,
                              target_x: float, target_y: float,
                              num_points: int = 30) -> List[Tuple[float, float]]:
        """
        Generate a curved path using cubic Bezier curve with random control points.

        Args:
            start_x (float): Starting X coordinate
            start_y (float): Starting Y coordinate
            target_x (float): Target X coordinate
            target_y (float): Target Y coordinate
            num_points (int): Number of points along the curve (default: 30)

        Returns:
            List of (x, y) tuples representing the path
        """
        # Calculate distance for control point offset
        distance = math.sqrt((target_x - start_x)**2 + (target_y - start_y)**2)

        # Generate two control points for cubic Bezier curve
        # Control points are offset perpendicular to the direct line
        # This creates a natural curve

        # Midpoint
        mid_x = (start_x + target_x) / 2
        mid_y = (start_y + target_y) / 2

        # Perpendicular offset (creates the curve)
        curve_intensity = random.uniform(0.15, 0.35)  # How much curve
        offset = distance * curve_intensity

        # Angle perpendicular to the direct line
        dx = target_x - start_x
        dy = target_y - start_y
        angle = math.atan2(dy, dx) + math.pi / 2  # +90 degrees

        # Control point 1 (1/3 along the path)
        cp1_x = start_x + dx * 0.33 + math.cos(angle) * offset * random.uniform(0.5, 1.0)
        cp1_y = start_y + dy * 0.33 + math.sin(angle) * offset * random.uniform(0.5, 1.0)

        # Control point 2 (2/3 along the path)
        cp2_x = start_x + dx * 0.67 + math.cos(angle) * offset * random.uniform(-1.0, -0.5)
        cp2_y = start_y + dy * 0.67 + math.sin(angle) * offset * random.uniform(-1.0, -0.5)

        # Generate points along the Bezier curve
        points = []
        for i in range(num_points):
            t = i / (num_points - 1)  # Parameter from 0 to 1

            # Cubic Bezier curve formula
            # B(t) = (1-t)³P0 + 3(1-t)²t*P1 + 3(1-t)t²P2 + t³P3
            x = (
                (1 - t)**3 * start_x +
                3 * (1 - t)**2 * t * cp1_x +
                3 * (1 - t) * t**2 * cp2_x +
                t**3 * target_x
            )

            y = (
                (1 - t)**3 * start_y +
                3 * (1 - t)**2 * t * cp1_y +
                3 * (1 - t) * t**2 * cp2_y +
                t**3 * target_y
            )

            points.append((x, y))

        return points
