"""
BrowserExploiter — confirm and exploit vulnerabilities using Playwright.

Confirms XSS via JS execution detection, tests CSRF via cross-origin forms,
validates auth bypass by comparing browser responses, and captures evidence.
"""

from __future__ import annotations

import base64
import os
import tempfile
from typing import Any

from senshi.utils.logger import get_logger

logger = get_logger("senshi.browser.exploiter")


class BrowserExploiter:
    """Use Playwright to confirm and exploit vulnerabilities."""

    def __init__(self, headless: bool = True) -> None:
        self.headless = headless
        self._playwright = None
        self._browser = None

    async def start(self) -> None:
        """Launch browser."""
        from playwright.async_api import async_playwright
        self._playwright = await async_playwright().start()
        self._browser = await self._playwright.chromium.launch(headless=self.headless)

    async def stop(self) -> None:
        """Close browser."""
        if self._browser:
            await self._browser.close()
        if self._playwright:
            await self._playwright.stop()

    async def confirm_xss(self, url: str, payload: str) -> dict[str, Any]:
        """
        Confirm XSS by checking if JavaScript executes in the browser.

        Returns:
            {"confirmed": bool, "evidence": list[str], "screenshot_path": str}
        """
        if not self._browser:
            await self.start()

        context = await self._browser.new_context()
        page = await context.new_page()

        evidence: list[str] = []

        # Listen for JS execution signals
        page.on("console", lambda msg: evidence.append(f"Console: {msg.text}"))
        page.on("dialog", lambda dialog: (
            evidence.append(f"Alert: {dialog.message}"),
            dialog.dismiss(),
        ))

        try:
            await page.goto(url, timeout=10000)
            await page.wait_for_timeout(2000)
        except Exception as e:
            logger.debug(f"Page load error: {e}")

        confirmed = len(evidence) > 0
        screenshot_path = ""

        if confirmed:
            screenshot_path = await self._take_screenshot(page, "xss_confirmed")

        await context.close()

        return {
            "confirmed": confirmed,
            "evidence": evidence,
            "screenshot_path": screenshot_path,
            "payload": payload,
            "url": url,
        }

    async def confirm_auth_bypass(
        self, url: str,
        with_auth_cookies: list[dict] | None = None,
        without_auth: bool = True,
    ) -> dict[str, Any]:
        """Compare responses with and without authentication."""
        if not self._browser:
            await self.start()

        # Authed request
        authed_ctx = await self._browser.new_context()
        if with_auth_cookies:
            await authed_ctx.add_cookies(with_auth_cookies)
        authed_page = await authed_ctx.new_page()
        try:
            await authed_page.goto(url, timeout=10000)
            authed_content = await authed_page.content()
            authed_title = await authed_page.title()
        except Exception:
            authed_content = ""
            authed_title = ""
        await authed_ctx.close()

        # Unauthed request
        unauthed_ctx = await self._browser.new_context()
        unauthed_page = await unauthed_ctx.new_page()
        try:
            await unauthed_page.goto(url, timeout=10000)
            unauthed_content = await unauthed_page.content()
            unauthed_title = await unauthed_page.title()
        except Exception:
            unauthed_content = ""
            unauthed_title = ""

        screenshot_path = await self._take_screenshot(unauthed_page, "auth_bypass")
        await unauthed_ctx.close()

        # Compare
        same_content = abs(len(authed_content) - len(unauthed_content)) < 100
        bypass_confirmed = same_content and len(unauthed_content) > 200

        return {
            "confirmed": bypass_confirmed,
            "evidence": [
                f"Authed content length: {len(authed_content)}",
                f"Unauthed content length: {len(unauthed_content)}",
                f"Authed title: {authed_title}",
                f"Unauthed title: {unauthed_title}",
            ],
            "screenshot_path": screenshot_path,
        }

    async def test_csrf(
        self, target_url: str, method: str, data: dict[str, str],
    ) -> dict[str, Any]:
        """Test CSRF by submitting a cross-origin form."""
        if not self._browser:
            await self.start()

        inputs = "".join(f'<input name="{k}" value="{v}">' for k, v in data.items())
        csrf_html = f"""
        <html><body>
        <form id="csrf" action="{target_url}" method="{method}">
            {inputs}
        </form>
        <script>document.getElementById('csrf').submit();</script>
        </body></html>
        """

        context = await self._browser.new_context()
        page = await context.new_page()

        responses_captured: list[dict] = []
        page.on("response", lambda r: responses_captured.append({
            "url": r.url, "status": r.status
        }))

        try:
            await page.set_content(csrf_html)
            await page.wait_for_timeout(3000)
        except Exception as e:
            logger.debug(f"CSRF test error: {e}")

        screenshot_path = await self._take_screenshot(page, "csrf_test")
        await context.close()

        # Check if the target URL was hit
        target_hit = any(target_url in r["url"] for r in responses_captured)
        success = any(r["status"] < 400 for r in responses_captured if target_url in r["url"])

        return {
            "confirmed": target_hit and success,
            "evidence": [f"Target hit: {target_hit}", f"Responses: {responses_captured[:5]}"],
            "screenshot_path": screenshot_path,
        }

    async def confirm_open_redirect(
        self, url: str, param: str, redirect_target: str = "https://evil.com",
    ) -> dict[str, Any]:
        """Test for open redirect."""
        if not self._browser:
            await self.start()

        context = await self._browser.new_context()
        page = await context.new_page()

        test_url = f"{url}?{param}={redirect_target}"
        final_url = ""

        try:
            response = await page.goto(test_url, timeout=10000)
            final_url = page.url
        except Exception:
            pass

        await context.close()

        redirected = redirect_target in final_url

        return {
            "confirmed": redirected,
            "evidence": [f"Final URL: {final_url}", f"Test URL: {test_url}"],
            "url": url,
            "payload": f"{param}={redirect_target}",
        }

    async def _take_screenshot(self, page: Any, prefix: str) -> str:
        """Take screenshot and save to temp file."""
        try:
            path = os.path.join(tempfile.gettempdir(), f"senshi_{prefix}_{id(page)}.png")
            await page.screenshot(path=path)
            return path
        except Exception:
            return ""
