from __future__ import annotations

import json
import socket
import threading
import time
from uuid import UUID

from playwright.sync_api import sync_playwright
import uvicorn

from app import app
from core.auth.models import CurrentUser
from v1.users.dependencies import get_current_user, get_user_service
from v1.users.schemas import UserResponse, UserUpdateRequest


class FakeUserService:
    """Fake service for E2E testing."""

    def __init__(self, user: UserResponse) -> None:
        self._user = user

    async def get_me(self) -> UserResponse:
        return self._user

    async def update_me(self, update: UserUpdateRequest) -> UserResponse:
        return UserResponse(
            id=self._user.id,
            username=(
                update.username if update.username is not None else self._user.username
            ),
            avatar_url=(
                update.avatar_url
                if update.avatar_url is not None
                else self._user.avatar_url
            ),
            bio=update.bio if update.bio is not None else self._user.bio,
        )

    async def get_by_username(self, username: str) -> UserResponse:
        return self._user


def _find_free_port() -> int:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.bind(("127.0.0.1", 0))
        return sock.getsockname()[1]


def _wait_for_port(host: str, port: int, timeout: float = 5.0) -> None:
    deadline = time.time() + timeout
    while time.time() < deadline:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            if sock.connect_ex((host, port)) == 0:
                return
        time.sleep(0.05)
    raise RuntimeError("Server did not start in time")


def _start_server(host: str, port: int):
    config = uvicorn.Config(app, host=host, port=port, log_level="info")
    server = uvicorn.Server(config)
    thread = threading.Thread(target=server.run, daemon=True)
    thread.start()
    _wait_for_port(host, port)
    return server, thread


def test_profile_flow_e2e() -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    user = UserResponse(
        id=str(user_id),
        username="demo",
        avatar_url=None,
        bio=None,
    )
    app.dependency_overrides[get_user_service] = lambda: FakeUserService(user)  # type: ignore[return-value]
    app.dependency_overrides[get_current_user] = lambda: CurrentUser(id=user_id)

    host = "127.0.0.1"
    port = _find_free_port()
    server, thread = _start_server(host, port)

    try:
        with sync_playwright() as playwright:
            request_context = playwright.request.new_context(
                base_url=f"http://{host}:{port}"
            )
            try:
                me = request_context.get("/api/v1/users/me")
                assert me.status == 200
                assert me.json()["username"] == "demo"

                updated = request_context.patch(
                    "/api/v1/users/me",
                    data=json.dumps({"username": "updated"}),
                    headers={"Content-Type": "application/json"},
                )
                assert updated.status == 200
                assert updated.json()["username"] == "updated"

                public = request_context.get("/api/v1/users/demo")
                assert public.status == 200
                assert public.json()["username"] == "demo"
            finally:
                request_context.dispose()
    finally:
        app.dependency_overrides = {}
        server.should_exit = True
        thread.join(timeout=5)
