import requests
from urllib.parse import quote
from concurrent.futures import ThreadPoolExecutor, as_completed

IP_ECHO_URL = "https://api.ipify.org"
REQUEST_TIMEOUT = 15
IP2LOCATION_API_KEY = "4A931194EBABD180692AEAC55FF700C6"


def parse_proxy(proxy_line: str):
    """
    Parse proxy format: host:port:username:password
    Returns: (host, port, username, password)
    """
    parts = proxy_line.strip().split(":")
    if len(parts) < 4:
        raise ValueError("Invalid proxy format (need host:port:user:pass)")
    host = parts[0]
    port = int(parts[1])
    username = parts[2]
    password = ":".join(parts[3:])
    return host, port, username, password


def get_proxy_dict(proxy_line: str):
    """
    Build a requests-compatible proxies dict for SOCKS5.
    """
    host, port, username, password = parse_proxy(proxy_line)
    # URL-encode username/password in case they contain special chars
    user = quote(username, safe="")
    pwd = quote(password, safe="")
    proxy_url = f"socks5h://{user}:{pwd}@{host}:{port}"
    return {"http": proxy_url, "https": proxy_url}


def get_public_ip(proxy_line: str) -> str:
    proxies = get_proxy_dict(proxy_line)
    r = requests.get(IP_ECHO_URL, proxies=proxies, timeout=REQUEST_TIMEOUT)
    r.raise_for_status()
    return r.text.strip()


def get_ip_info(ip: str):
    """
    Returns: (fraud_score, country, region, city, zip_code)
    """
    r = requests.get(
        "https://api.ip2location.io/",
        params={
            "key": IP2LOCATION_API_KEY,
            "ip": ip,
            "format": "json",
            "source": "fraud"
        },
        timeout=REQUEST_TIMEOUT
    )
    r.raise_for_status()
    d = r.json()

    if "fraud_score" not in d:
        raise RuntimeError(f"Bad IP2Location response: {d}")

    fraud_score = int(d["fraud_score"])
    country = d.get("country_name", "Unknown")
    region = d.get("region_name", "Unknown")
    city = d.get("city_name", "Unknown")
    zip_code = d.get("zip_code") or d.get("zipcode") or "Unknown"

    return fraud_score, country, region, city, zip_code


def check_single_proxy(proxy_line: str, ip_cache: dict = None):
    """
    Check a single proxy and return a dict with results.
    """
    if ip_cache is None:
        ip_cache = {}

    result = {
        "proxy": proxy_line,
        "ip": None,
        "country": None,
        "region": None,
        "city": None,
        "zip_code": None,
        "fraud_score": None,
        "is_clean": False,
        "status": "failed",
        "error": None
    }

    try:
        ip = get_public_ip(proxy_line)
        result["ip"] = ip

        if ip not in ip_cache:
            ip_cache[ip] = get_ip_info(ip)

        score, country, region, city, zip_code = ip_cache[ip]
        result["fraud_score"] = score
        result["country"] = country
        result["region"] = region
        result["city"] = city
        result["zip_code"] = zip_code
        result["is_clean"] = (score == 0)
        result["status"] = "success"

    except Exception as e:
        result["error"] = str(e)
        result["status"] = "failed"

    return result


def check_proxies_concurrently(proxy_lines: list, max_workers: int = 10, progress_callback=None):
    """
    Check multiple proxies concurrently. Optionally call progress_callback(index, total, result).
    """
    ip_cache = {}
    results = []
    total = len(proxy_lines)

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        future_to_proxy = {
            executor.submit(check_single_proxy, proxy, ip_cache): proxy
            for proxy in proxy_lines
        }
        completed = 0
        for future in as_completed(future_to_proxy):
            result = future.result()
            results.append(result)
            completed += 1
            if progress_callback:
                progress_callback(completed, total, result)

    return results
