import socket
import threading
import time

import httpx
import pytest
import uvicorn

from sitedrop.server.app import create_app
from sitedrop.server.auth import hash_password
from sitedrop.server.config import ServerConfig


def _free_port():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("127.0.0.1", 0))
        return s.getsockname()[1]


@pytest.fixture
def live_server(tmp_path):
    port = _free_port()
    sites_dir = tmp_path / "sites"
    sites_dir.mkdir()
    config = ServerConfig(
        password_hash=hash_password("testpass"),
        jwt_secret="integration-test-secret-long-enough-for-hs256",
        sites_dir=str(sites_dir),
        host="127.0.0.1",
        port=port,
    )
    app = create_app(config)
    server_config = uvicorn.Config(app, host="127.0.0.1", port=port, log_level="error")
    server = uvicorn.Server(server_config)

    thread = threading.Thread(target=server.run, daemon=True)
    thread.start()

    base_url = f"http://127.0.0.1:{port}"
    deadline = time.time() + 5
    while time.time() < deadline:
        try:
            resp = httpx.get(f"{base_url}/api/health", timeout=1)
            if resp.status_code == 200:
                break
        except httpx.ConnectError:
            time.sleep(0.1)
    else:
        raise RuntimeError("Server did not start in time")

    yield base_url

    server.should_exit = True
    thread.join(timeout=5)


def test_full_round_trip(live_server):
    base = live_server

    # Health check
    resp = httpx.get(f"{base}/api/health")
    assert resp.status_code == 200
    assert resp.json()["status"] == "ok"

    # Auth with wrong password
    resp = httpx.post(f"{base}/api/auth", json={"password": "wrong"})
    assert resp.status_code == 401

    # Auth with correct password
    resp = httpx.post(f"{base}/api/auth", json={"password": "testpass"})
    assert resp.status_code == 200
    token = resp.json()["token"]
    headers = {"Authorization": f"Bearer {token}"}

    # List (empty)
    resp = httpx.get(f"{base}/api/sites", headers=headers)
    assert resp.status_code == 200
    assert resp.json()["sites"] == []

    # Upload
    resp = httpx.put(
        f"{base}/api/sites/hello",
        content=b"<h1>Hello World</h1>",
        headers=headers,
    )
    assert resp.status_code == 200
    assert resp.json()["name"] == "hello"

    # List (1 site)
    resp = httpx.get(f"{base}/api/sites", headers=headers)
    assert resp.status_code == 200
    assert len(resp.json()["sites"]) == 1

    # GET served site (public, no auth)
    resp = httpx.get(f"{base}/hello")
    assert resp.status_code == 200
    assert "<h1>Hello World</h1>" in resp.text

    # Verify security headers on API response
    resp = httpx.get(f"{base}/api/health")
    assert resp.headers["x-content-type-options"] == "nosniff"
    assert resp.headers["x-frame-options"] == "DENY"

    # Delete
    resp = httpx.delete(f"{base}/api/sites/hello", headers=headers)
    assert resp.status_code == 200

    # Verify 404 after delete
    resp = httpx.get(f"{base}/hello")
    assert resp.status_code == 404


def test_auth_required_without_token(live_server):
    base = live_server

    endpoints = [
        ("get", f"{base}/api/sites"),
        ("put", f"{base}/api/sites/test"),
        ("delete", f"{base}/api/sites/test"),
        ("post", f"{base}/api/password"),
    ]
    for method, url in endpoints:
        resp = getattr(httpx, method)(url)
        assert resp.status_code in (401, 403), (
            f"{method.upper()} {url} returned {resp.status_code}"
        )


def test_password_change_flow(live_server):
    base = live_server

    # Auth with original password
    resp = httpx.post(f"{base}/api/auth", json={"password": "testpass"})
    assert resp.status_code == 200
    token = resp.json()["token"]
    headers = {"Authorization": f"Bearer {token}"}

    # Change password
    resp = httpx.post(
        f"{base}/api/password",
        json={"current_password": "testpass", "new_password": "newpass123"},
        headers=headers,
    )
    assert resp.status_code == 200
    data = resp.json()
    assert "token" in data
    new_token = data["token"]

    # Old token should be rejected (JWT secret was rotated)
    resp = httpx.get(f"{base}/api/sites", headers=headers)
    assert resp.status_code == 401

    # New token should work
    new_headers = {"Authorization": f"Bearer {new_token}"}
    resp = httpx.get(f"{base}/api/sites", headers=new_headers)
    assert resp.status_code == 200

    # Old password fails
    resp = httpx.post(f"{base}/api/auth", json={"password": "testpass"})
    assert resp.status_code == 401

    # New password works
    resp = httpx.post(f"{base}/api/auth", json={"password": "newpass123"})
    assert resp.status_code == 200
