from __future__ import annotations

import asyncio
import json
import logging
from typing import Callable

import httpx

log = logging.getLogger(__name__)

# Secondary fallback endpoints for IP resolution
_IP_FALLBACKS = [
    "https://api.ipify.org?format=json",
    "https://httpbin.org/ip",
    "https://ifconfig.me/ip",
]


def _parse_ip(text: str) -> str | None:
    """Parse an IP string from JSON or plain-text response body."""
    body = text.strip()
    if not body:
        return None
    if body.startswith("{"):
        try:
            d = json.loads(body)
            ip = d.get("ip") or d.get("origin") or d.get("query")
            return str(ip).split(",")[0].strip() if ip else None
        except Exception:
            return None
    # Plain-text: must look like an IP (short, no HTML)
    if len(body) < 50 and "<" not in body:
        candidate = body.split()[0].strip()
        if candidate.count(".") >= 3 or ":" in candidate:
            return candidate
    return None


async def validate_proxies(
    proxy_urls: list[str],
    check_url: str,
    concurrency: int,
    timeout_seconds: float,
    on_progress: Callable[[int, int], None] | None = None,
) -> list[str]:
    sem = asyncio.Semaphore(max(1, concurrency))
    ok: list[str] = []
    lock = asyncio.Lock()
    total = len(proxy_urls)
    done = 0

    async def one(u: str) -> None:
        nonlocal done
        async with sem:
            good = await _check_one(u, check_url, timeout_seconds)
        async with lock:
            done += 1
            if good:
                ok.append(u)
            if on_progress:
                on_progress(done, total)

    await asyncio.gather(*(one(u) for u in proxy_urls))
    return ok


async def _check_one(proxy_url: str, check_url: str, timeout_seconds: float) -> bool:
    t = httpx.Timeout(timeout_seconds, connect=min(8.0, timeout_seconds))
    try:
        async with httpx.AsyncClient(
            proxy=proxy_url,
            timeout=t,
            verify=False,          # many proxies have self-signed or no TLS
            follow_redirects=True,
        ) as c:
            r = await c.get(check_url)
            return r.status_code == 200 and len(r.content) > 0
    except Exception:
        return False


async def check_chain_exit_ip(
    listen_proxy: str,
    check_url: str,
    timeout_seconds: float,
) -> str | None:
    """Query the IP-check URL through the local chain proxy.
    Returns the exit IP string on success, or None on failure.
    Tries the configured URL first, then falls back to alternatives."""
    urls = [check_url] + [u for u in _IP_FALLBACKS if u != check_url]
    t = httpx.Timeout(timeout_seconds, connect=min(10.0, timeout_seconds))
    try:
        async with httpx.AsyncClient(
            proxy=listen_proxy,
            timeout=t,
            verify=False,
            follow_redirects=True,
        ) as c:
            for url in urls:
                try:
                    r = await c.get(url)
                    if r.status_code == 200:
                        ip = _parse_ip(r.text)
                        if ip:
                            return ip
                except Exception:
                    continue
    except Exception:
        pass
    return None


async def get_direct_ip(check_url: str, timeout_seconds: float = 15.0) -> str | None:
    """Get our own exit IP without the proxy chain (so we can compare)."""
    urls = [check_url] + [u for u in _IP_FALLBACKS if u != check_url]
    t = httpx.Timeout(timeout_seconds)
    try:
        async with httpx.AsyncClient(
            timeout=t,
            verify=False,
            follow_redirects=True,
        ) as c:
            for url in urls:
                try:
                    r = await c.get(url)
                    if r.status_code == 200:
                        ip = _parse_ip(r.text)
                        if ip:
                            return ip
                except Exception:
                    continue
    except Exception:
        pass
    return None
