"""Offline-тесты admin proxy-pool ручек (#2161).

Покрытие (db мокается, NO live network/DB):
  - POST /api/v1/admin/proxies/bulk — UPSERT-счётчики, валидация affinity/kind
  - GET  /api/v1/admin/proxies      — маскировка пароля, фильтры
  - PATCH /api/v1/admin/proxies/{id} — enable/disable, 404
"""

from __future__ import annotations

import os

os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")

from datetime import UTC, datetime
from typing import Any
from unittest.mock import MagicMock

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient


@pytest.fixture
def db() -> MagicMock:
    return MagicMock()


@pytest.fixture
def client(db: MagicMock) -> TestClient:
    from app.api.v1 import admin as admin_module
    from app.core.db import get_db

    app = FastAPI()
    app.include_router(admin_module.router, prefix="/api/v1/admin")

    def fake_db() -> Any:
        yield db

    app.dependency_overrides[get_db] = fake_db
    return TestClient(app)


def _scalar_result(value: object) -> MagicMock:
    res = MagicMock()
    res.scalar.return_value = value
    return res


# ── _mask_proxy_url unit ─────────────────────────────────────────────────────


def test_mask_proxy_url_hides_password() -> None:
    from app.api.v1.admin import _mask_proxy_url

    assert _mask_proxy_url("socks5://user:secret@host:1080") == "socks5://user:***@host:1080"
    assert _mask_proxy_url("http://u:p@1.2.3.4:8080") == "http://u:***@1.2.3.4:8080"


def test_mask_proxy_url_no_password_untouched() -> None:
    from app.api.v1.admin import _mask_proxy_url

    assert _mask_proxy_url("http://host:8080") == "http://host:8080"
    assert _mask_proxy_url(None) is None
    assert _mask_proxy_url("") == ""


# ── POST /proxies/bulk ───────────────────────────────────────────────────────


def test_bulk_upsert_counts_inserted_and_updated(client: TestClient, db: MagicMock) -> None:
    # первая строка вставлена (xmax=0 → True), вторая обновлена (False)
    db.execute.side_effect = [_scalar_result(True), _scalar_result(False)]
    r = client.post(
        "/api/v1/admin/proxies/bulk",
        json={
            "proxies": [
                {"url": "http://u:p@host1:8080", "provider_affinity": "avito"},
                {"url": "http://u:p@host2:8080", "provider_affinity": "cian", "kind": "socks5"},
            ]
        },
    )
    assert r.status_code == 200, r.text
    assert r.json() == {"inserted": 1, "updated": 1}
    assert db.commit.called


def test_bulk_invalid_affinity_422(client: TestClient) -> None:
    r = client.post(
        "/api/v1/admin/proxies/bulk",
        json={"proxies": [{"url": "http://host:8080", "provider_affinity": "garbage"}]},
    )
    assert r.status_code == 422


def test_bulk_invalid_kind_422(client: TestClient) -> None:
    r = client.post(
        "/api/v1/admin/proxies/bulk",
        json={"proxies": [{"url": "http://host:8080", "kind": "ftp"}]},
    )
    assert r.status_code == 422


def test_bulk_empty_list_422(client: TestClient) -> None:
    r = client.post("/api/v1/admin/proxies/bulk", json={"proxies": []})
    assert r.status_code == 422


def test_bulk_defaults_affinity_any(client: TestClient, db: MagicMock) -> None:
    db.execute.side_effect = [_scalar_result(True)]
    r = client.post(
        "/api/v1/admin/proxies/bulk",
        json={"proxies": [{"url": "http://host:8080"}]},
    )
    assert r.status_code == 200
    # affinity по умолчанию 'any' попал в параметры INSERT
    _, kwargs = db.execute.call_args
    assert kwargs is not None
    params = db.execute.call_args.args[1]
    assert params["aff"] == "any"
    assert params["kind"] == "http"


# ── GET /proxies ─────────────────────────────────────────────────────────────


def _proxy_db_row(**over: Any) -> dict[str, Any]:
    base: dict[str, Any] = {
        "id": 1,
        "label": "test",
        "url": "socks5://user:secret@host:1080",
        "kind": "socks5",
        "provider_affinity": "avito",
        "rotate_url": "http://user:secret@rot:9000/changeip",
        "enabled": True,
        "consecutive_fails": 0,
        "exit_ip": "1.2.3.4",
        "latency_ms": 120,
        "last_check_at": datetime(2026, 7, 1, tzinfo=UTC),
        "last_ok_at": datetime(2026, 7, 1, tzinfo=UTC),
        "leased_by": None,
        "leased_at": None,
        "geo": "RU",
        "operator": "beeline",
        "expires_at": None,
        "created_at": datetime(2026, 6, 1, tzinfo=UTC),
        "updated_at": datetime(2026, 7, 1, tzinfo=UTC),
    }
    base.update(over)
    return base


def test_list_masks_password(client: TestClient, db: MagicMock) -> None:
    result = MagicMock()
    result.mappings.return_value.all.return_value = [_proxy_db_row()]
    db.execute.return_value = result

    r = client.get("/api/v1/admin/proxies")
    assert r.status_code == 200, r.text
    rows = r.json()
    assert len(rows) == 1
    assert rows[0]["url"] == "socks5://user:***@host:1080"
    assert rows[0]["rotate_url"] == "http://user:***@rot:9000/changeip"
    assert "secret" not in r.text


def test_list_passes_filters(client: TestClient, db: MagicMock) -> None:
    result = MagicMock()
    result.mappings.return_value.all.return_value = []
    db.execute.return_value = result

    r = client.get("/api/v1/admin/proxies", params={"provider": "cian", "enabled": "false"})
    assert r.status_code == 200
    params = db.execute.call_args.args[1]
    assert params["provider"] == "cian"
    assert params["enabled"] is False


# ── PATCH /proxies/{id} ──────────────────────────────────────────────────────


def test_patch_disable(client: TestClient, db: MagicMock) -> None:
    result = MagicMock()
    result.mappings.return_value.fetchone.return_value = _proxy_db_row(enabled=False)
    db.execute.return_value = result

    r = client.patch("/api/v1/admin/proxies/1", json={"enabled": False})
    assert r.status_code == 200, r.text
    assert r.json()["enabled"] is False
    assert db.commit.called


def test_patch_not_found_404(client: TestClient, db: MagicMock) -> None:
    result = MagicMock()
    result.mappings.return_value.fetchone.return_value = None
    db.execute.return_value = result

    r = client.patch("/api/v1/admin/proxies/999", json={"enabled": True})
    assert r.status_code == 404
