from __future__ import annotations

import asyncio
import json
import time

from fastapi import Request as FRequest
from fastapi.responses import JSONResponse, Response


def register_browser_extension_routes(
    fastapi_app,
    auth_token: str,
    load_app_prefs,
    save_app_prefs,
    log,
) -> None:
    browser_extension_hits: dict[str, list[float]] = {}
    body_max_bytes = 1_048_576
    text_max_chars = 500_000
    rate_limit_per_minute = 30
    prefs_key = "browser_extension"

    def _origin(request: FRequest) -> tuple[str, str]:
        origin = (request.headers.get("origin") or "").strip()
        if origin.startswith("chrome-extension://"):
            ext_id = origin[len("chrome-extension://"):].split("/", 1)[0].strip()
            return origin, ext_id
        return origin, ""

    def _prefs() -> dict:
        prefs = load_app_prefs()
        raw = prefs.get(prefs_key)
        return raw if isinstance(raw, dict) else {}

    def _pinned_id() -> str:
        return str(_prefs().get("pinned_extension_id") or "").strip()

    def _pin_id(ext_id: str) -> None:
        ext = str(ext_id or "").strip()
        if not ext:
            return
        prefs = load_app_prefs()
        section = prefs.get(prefs_key)
        if not isinstance(section, dict):
            section = {}
        if not str(section.get("pinned_extension_id") or "").strip():
            section["pinned_extension_id"] = ext
            prefs[prefs_key] = section
            try:
                save_app_prefs(prefs)
            except Exception:
                log.debug("failed to pin browser extension id", exc_info=True)

    def _origin_allowed(request: FRequest, *, pin: bool = False) -> bool:
        origin, ext_id = _origin(request)
        if not (
            origin.startswith("chrome-extension://")
            or origin.startswith("http://localhost:")
            or origin.startswith("http://127.0.0.1:")
        ):
            return False
        if origin.startswith("chrome-extension://"):
            pinned = _pinned_id()
            if pinned and ext_id != pinned:
                return False
            if pin:
                _pin_id(ext_id)
        return True

    def _cors_headers(request: FRequest) -> dict[str, str]:
        origin = (request.headers.get("origin") or "").strip()
        if not origin:
            return {}
        if not _origin_allowed(request, pin=False):
            return {}
        return {
            "Access-Control-Allow-Origin": origin,
            "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
            "Access-Control-Allow-Headers": "Content-Type, X-Auth-Token",
            "Access-Control-Max-Age": "600",
            "Vary": "Origin",
        }

    def _json_response(request: FRequest, content: dict, status_code: int = 200) -> JSONResponse:
        return JSONResponse(content=content, status_code=status_code, headers=_cors_headers(request))

    def _client_allowed(request: FRequest) -> bool:
        host = ((request.client.host if request.client else "") or "").strip()
        if not host or host == "testclient":
            return True
        if host in ("localhost", "127.0.0.1", "::1"):
            return True
        try:
            import ipaddress

            ip = ipaddress.ip_address(host)
            return bool(ip.is_loopback)
        except ValueError:
            return False

    def _path_allowed(request: FRequest) -> bool:
        path = request.url.path or ""
        raw = ((request.scope or {}).get("raw_path") or b"").decode("latin-1", "ignore").lower()
        tail = path[1:]
        if "\x00" in path or ".." in path or "//" in tail:
            return False
        if "%2e" in raw or "%00" in raw:
            return False
        return path.startswith("/browser_extension/")

    def _request_allowed(request: FRequest, *, pin_origin: bool = False) -> JSONResponse | None:
        if not _path_allowed(request):
            return _json_response(request, {"ok": False, "error": "invalid_path"}, status_code=400)
        if not _client_allowed(request):
            return _json_response(request, {"ok": False, "error": "forbidden"}, status_code=403)
        if request.headers.get("x-auth-token") != auth_token:
            return _json_response(request, {"ok": False, "error": "forbidden"}, status_code=403)
        origin = (request.headers.get("origin") or "").strip()
        if origin and not _origin_allowed(request, pin=pin_origin):
            return _json_response(request, {"ok": False, "error": "forbidden_origin"}, status_code=403)
        return None

    async def _payload(request: FRequest) -> tuple[dict | None, JSONResponse | None]:
        if (request.headers.get("transfer-encoding") or "").strip().lower() == "chunked":
            return None, _json_response(request, {"ok": False, "error": "chunked_not_allowed"}, status_code=413)
        try:
            content_length = int(request.headers.get("content-length") or "0")
        except ValueError:
            content_length = 0
        if content_length > body_max_bytes:
            return None, _json_response(request, {"ok": False, "error": "body_too_large"}, status_code=413)
        body = await request.body()
        if len(body) > body_max_bytes:
            return None, _json_response(request, {"ok": False, "error": "body_too_large"}, status_code=413)
        try:
            parsed = json.loads(body.decode("utf-8")) if body else {}
        except Exception:
            return None, _json_response(request, {"ok": False, "error": "invalid_json"}, status_code=400)
        if not isinstance(parsed, dict):
            return None, _json_response(request, {"ok": False, "error": "invalid_payload"}, status_code=400)
        return parsed, None

    def _rate_limited(request: FRequest) -> bool:
        host = ((request.client.host if request.client else "") or "unknown").strip() or "unknown"
        now = time.time()
        bucket = browser_extension_hits.setdefault(host, [])
        cutoff = now - 60
        while bucket and bucket[0] < cutoff:
            bucket.pop(0)
        if len(bucket) >= rate_limit_per_minute:
            return True
        bucket.append(now)
        return False

    try:
        from browser_extension_capture import set_agent_capture_enabled

        set_agent_capture_enabled(bool(_prefs().get("agent_capture_enabled", False)))
    except Exception:
        pass

    @fastapi_app.options("/browser_extension/{rest:path}")
    async def browser_extension_options(request: FRequest, rest: str):
        return Response(status_code=204, headers=_cors_headers(request))

    @fastapi_app.get("/browser_extension/status")
    async def browser_extension_status(request: FRequest):
        denied = _request_allowed(request, pin_origin=False)
        if denied is not None:
            return denied
        from browser_extension_capture import agent_capture_enabled

        prefs = _prefs()
        return _json_response(
            request,
            {
                "ok": True,
                "agent_capture_enabled": agent_capture_enabled(),
                "pinned_extension_id": str(prefs.get("pinned_extension_id") or ""),
            },
        )

    @fastapi_app.post("/browser_extension/settings")
    async def browser_extension_settings(request: FRequest):
        denied = _request_allowed(request, pin_origin=True)
        if denied is not None:
            return denied
        payload, error = await _payload(request)
        if error is not None:
            return error
        payload = payload or {}
        from browser_extension_capture import set_agent_capture_enabled

        prefs = load_app_prefs()
        section = prefs.get(prefs_key)
        if not isinstance(section, dict):
            section = {}
        if "agent_capture_enabled" in payload:
            section["agent_capture_enabled"] = bool(payload.get("agent_capture_enabled"))
            set_agent_capture_enabled(bool(section["agent_capture_enabled"]))
        if bool(payload.get("forget_extension_binding")):
            section.pop("pinned_extension_id", None)
        prefs[prefs_key] = section
        try:
            save_app_prefs(prefs)
        except Exception:
            pass
        return _json_response(
            request,
            {
                "ok": True,
                "agent_capture_enabled": bool(section.get("agent_capture_enabled", False)),
                "pinned_extension_id": str(section.get("pinned_extension_id") or ""),
            },
        )

    @fastapi_app.get("/browser_extension/pending_job")
    async def browser_extension_pending_job(request: FRequest):
        denied = _request_allowed(request, pin_origin=True)
        if denied is not None:
            return denied
        try:
            from browser_extension_capture import pending_job_for_extension

            job = pending_job_for_extension()
        except Exception as exc:
            code = getattr(exc, "code", "error")
            status_code = int(getattr(exc, "status_code", 500) or 500)
            return _json_response(request, {"ok": False, "error": code}, status_code=status_code)
        return _json_response(request, {"ok": True, "job": job})

    @fastapi_app.post("/browser_extension/claim")
    async def browser_extension_claim(request: FRequest):
        denied = _request_allowed(request, pin_origin=True)
        if denied is not None:
            return denied
        payload, error = await _payload(request)
        if error is not None:
            return error
        try:
            from browser_extension_capture import claim_job

            job = claim_job(str((payload or {}).get("job_id") or ""))
        except Exception as exc:
            code = getattr(exc, "code", "error")
            status_code = int(getattr(exc, "status_code", 500) or 500)
            return _json_response(request, {"ok": False, "error": code}, status_code=status_code)
        return _json_response(request, {"ok": True, "job": job})

    @fastapi_app.post("/browser_extension/cancel")
    async def browser_extension_cancel(request: FRequest):
        denied = _request_allowed(request, pin_origin=True)
        if denied is not None:
            return denied
        payload, error = await _payload(request)
        if error is not None:
            return error
        try:
            from browser_extension_capture import mark_failed

            job = mark_failed(
                str((payload or {}).get("job_id") or ""),
                str((payload or {}).get("reason") or "user_cancel"),
                decision="user_cancel",
            )
        except Exception as exc:
            code = getattr(exc, "code", "error")
            status_code = int(getattr(exc, "status_code", 500) or 500)
            return _json_response(request, {"ok": False, "error": code}, status_code=status_code)
        return _json_response(request, {"ok": True, "job": job})

    @fastapi_app.post("/browser_extension/disable")
    async def browser_extension_disable(request: FRequest):
        denied = _request_allowed(request, pin_origin=False)
        if denied is not None:
            return denied
        from browser_extension_capture import set_agent_capture_enabled

        set_agent_capture_enabled(False)
        prefs = load_app_prefs()
        section = prefs.get(prefs_key)
        if not isinstance(section, dict):
            section = {}
        section["agent_capture_enabled"] = False
        prefs[prefs_key] = section
        try:
            save_app_prefs(prefs)
        except Exception:
            pass
        return _json_response(request, {"ok": True, "agent_capture_enabled": False})

    @fastapi_app.post("/browser_extension/capture")
    async def browser_extension_capture(request: FRequest):
        denied = _request_allowed(request, pin_origin=True)
        if denied is not None:
            return denied
        if _rate_limited(request):
            return _json_response(request, {"ok": False, "error": "rate_limited"}, status_code=429)
        payload, error = await _payload(request)
        if error is not None:
            return error
        payload = payload or {}
        url = str(payload.get("url") or "").strip()
        final_url = str(payload.get("final_url") or url).strip()
        title = str(payload.get("title") or "").strip()
        text = payload.get("text")
        warning = str(payload.get("warning") or "").strip()
        job_id = str(payload.get("job_id") or "").strip()
        project_id = str(payload.get("project_id") or "").strip()
        if not url.startswith(("http://", "https://")):
            return _json_response(request, {"ok": False, "error": "invalid_url"}, status_code=400)
        if not isinstance(text, str):
            return _json_response(request, {"ok": False, "error": "missing_text"}, status_code=400)
        if len(text) > text_max_chars:
            return _json_response(request, {"ok": False, "error": "text_too_large"}, status_code=413)
        from browser_extension_capture import mark_complete, set_capture

        capture = {
            "url": url,
            "final_url": final_url,
            "title": title,
            "text": text,
            "warning": warning,
            "text_chars": len(text),
        }
        if job_id:
            try:
                job = mark_complete(job_id, capture)
            except Exception as exc:
                code = getattr(exc, "code", "error")
                status_code = int(getattr(exc, "status_code", 500) or 500)
                return _json_response(request, {"ok": False, "error": code}, status_code=status_code)
            result = job.get("result") or {}
            return _json_response(
                request,
                {
                    "ok": True,
                    "job_id": job_id,
                    "captured_at": result.get("captured_at"),
                    "text_chars": len(text),
                },
            )
        stored = set_capture(capture)
        note_id = ""
        if project_id:
            try:
                from chat_storage import create_capture

                body_parts = []
                if title:
                    body_parts.append(f"# {title}")
                    body_parts.append("")
                body_parts.append(f"Source: {final_url or url}")
                body_parts.append("")
                body_parts.append(text)
                note = await asyncio.to_thread(
                    create_capture,
                    "\n".join(body_parts),
                    source="browser_extension",
                    tags=["web_clip"],
                    metadata={
                        "source_url": final_url or url,
                        "original_url": url,
                        "clip_provider": "browser_extension",
                        "warning": warning,
                        "triage_state": "filed",
                    },
                    project_id=project_id,
                    title=title or final_url or url,
                )
                note_id = str(note.get("id") or "")
            except Exception:
                log.debug("Failed to persist browser capture into Vault", exc_info=True)
        return _json_response(
            request,
            {
                "ok": True,
                "captured_at": stored.get("captured_at"),
                "text_chars": len(text),
                "note_id": note_id,
            },
        )
