#!/usr/bin/env python3
"""
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🌐 Internet Connection & Speed Checker
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Programmer: Sam Nikzad
Website: https://avasam.ir
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Tests websites for:
- Basic connectivity (TCP/HTTP)
- DNS resolution
- TLS/SSL handshake
- Speed measurement
- Network restrictions

Restriction Types Detected:
1. DNS Blocking - Wrong IP or NXDOMAIN returned
2. IP Blocking - Direct IP access blocked
3. SNI Filtering - TLS handshake blocked based on hostname
4. DPI - Deep Packet Inspection
5. TCP RST - Connection forcefully reset
6. HTTP Filtering - HTTP requests blocked/redirected
"""

__author__ = "Sam Nikzad"
__website__ = "https://avasam.ir"
__version__ = "1.0.0"

import socket
import ssl
import time
import subprocess
import urllib.request
import urllib.error
from dataclasses import dataclass
from typing import Optional, Tuple, List

TIMEOUT = 10

# ═══════════════════════════════════════════════════════════════
# Data Classes
# ═══════════════════════════════════════════════════════════════

@dataclass
class TestResult:
    website: str
    dns_resolved: bool
    dns_ip: Optional[str]
    dns_time_ms: float
    tcp_connected: bool
    tcp_time_ms: float
    tls_connected: bool
    tls_time_ms: float
    http_accessible: bool
    http_status: Optional[int]
    http_time_ms: float
    total_speed_ms: float
    potential_blocks: List[str]
    restriction_level: str

# ═══════════════════════════════════════════════════════════════
# Emoji Constants
# ═══════════════════════════════════════════════════════════════

class E:
    CHECK = "✅"
    CROSS = "❌"
    WARN = "⚠️"
    GLOBE = "🌐"
    ROCKET = "🚀"
    SNAIL = "🐌"
    TURTLE = "🐢"
    LOCK = "🔒"
    UNLOCK = "🔓"
    DNS = "📡"
    SPEED = "⚡"
    BLOCK = "🚫"
    SHIELD = "🛡️"
    CLOCK = "⏱️"
    CHAIN = "🔗"
    FIRE = "🔥"
    EYES = "👀"
    DET = "🕵️"
    DEV = "👨‍💻"
    LINK = "🔗"
    INPUT = "📝"

# ═══════════════════════════════════════════════════════════════
# Testing Functions
# ═══════════════════════════════════════════════════════════════

def test_dns(hostname: str) -> Tuple[bool, Optional[str], float]:
    start = time.time()
    try:
        ip = socket.gethostbyname(hostname)
        return True, ip, (time.time() - start) * 1000
    except:
        return False, None, (time.time() - start) * 1000


def test_tcp(ip: str, port: int = 443) -> Tuple[bool, float]:
    start = time.time()
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(TIMEOUT)
        sock.connect((ip, port))
        sock.close()
        return True, (time.time() - start) * 1000
    except:
        return False, (time.time() - start) * 1000


def test_tls(hostname: str, ip: str) -> Tuple[bool, float, str]:
    start = time.time()
    try:
        ctx = ssl.create_default_context()
        with socket.create_connection((ip, 443), timeout=TIMEOUT) as sock:
            with ctx.wrap_socket(sock, server_hostname=hostname) as ssock:
                ssock.getpeercert()
                return True, (time.time() - start) * 1000, "OK"
    except ssl.SSLError as e:
        err = str(e)
        if "HANDSHAKE_FAILURE" in err:
            return False, (time.time() - start) * 1000, "SNI_BLOCKED"
        elif "CERTIFICATE_VERIFY_FAILED" in err:
            return False, (time.time() - start) * 1000, "MITM"
        return False, (time.time() - start) * 1000, "SSL_ERROR"
    except ConnectionResetError:
        return False, (time.time() - start) * 1000, "TCP_RST"
    except socket.timeout:
        return False, (time.time() - start) * 1000, "TIMEOUT"
    except:
        return False, (time.time() - start) * 1000, "ERROR"


def test_http(hostname: str) -> Tuple[bool, Optional[int], float]:
    start = time.time()
    try:
        req = urllib.request.Request(
            f"https://{hostname}",
            headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
        )
        with urllib.request.urlopen(req, timeout=TIMEOUT) as res:
            return True, res.getcode(), (time.time() - start) * 1000
    except urllib.error.HTTPError as e:
        return False, e.code, (time.time() - start) * 1000
    except:
        return False, None, (time.time() - start) * 1000


def test_openssl(hostname: str) -> Tuple[bool, str]:
    try:
        cmd = f"echo 'Q' | openssl s_client -connect {hostname}:443 -servername {hostname} 2>&1 | head -20"
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=TIMEOUT)
        output = result.stdout + result.stderr
        if "CONNECTED" in output:
            return True, "CONNECTED" + (" (Verified)" if "verify return:1" in output else "")
        return False, "FAILED"
    except:
        return False, "ERROR"


# ═══════════════════════════════════════════════════════════════
# Analysis
# ═══════════════════════════════════════════════════════════════

def analyze(result: TestResult) -> List[str]:
    blocks = []
    if not result.dns_resolved:
        blocks.append("DNS_BLOCKING")
    if result.dns_resolved and not result.tcp_connected:
        blocks.append("IP_BLOCKING")
    if result.tcp_connected and not result.tls_connected:
        blocks.append("SNI_FILTERING")
    if result.tls_connected and not result.http_accessible:
        blocks.append("HTTP_FILTERING")
    if result.http_status == 403:
        blocks.append("GEO_RESTRICTION")
    if result.http_status == 451:
        blocks.append("LEGAL_BLOCKING")
    return blocks


def get_level(blocks: List[str]) -> str:
    if not blocks:
        return "none"
    if any(b in blocks for b in ["DNS_BLOCKING", "IP_BLOCKING", "SNI_FILTERING"]):
        return "full"
    return "partial"


def get_speed(ms: float) -> Tuple[str, str]:
    if ms < 500:
        return "GOOD", E.ROCKET
    elif ms < 1500:
        return "MEDIUM", E.TURTLE
    return "BAD", E.SNAIL


def check_website(hostname: str) -> TestResult:
    blocks = []

    dns_ok, dns_ip, dns_time = test_dns(hostname)

    tcp_ok, tcp_time = (False, 0.0)
    if dns_ip:
        tcp_ok, tcp_time = test_tcp(dns_ip)

    tls_ok, tls_time = (False, 0.0)
    if tcp_ok and dns_ip:
        tls_ok, tls_time, tls_err = test_tls(hostname, dns_ip)
        if not tls_ok:
            if "RST" in tls_err:
                blocks.append("TCP_RST")
            elif "SNI" in tls_err:
                blocks.append("SNI_FILTERING")
            elif "MITM" in tls_err:
                blocks.append("MITM_ATTACK")

    http_ok, http_status, http_time = test_http(hostname)
    total = dns_time + tcp_time + tls_time + http_time

    result = TestResult(
        website=hostname,
        dns_resolved=dns_ok,
        dns_ip=dns_ip,
        dns_time_ms=dns_time,
        tcp_connected=tcp_ok,
        tcp_time_ms=tcp_time,
        tls_connected=tls_ok,
        tls_time_ms=tls_time,
        http_accessible=http_ok,
        http_status=http_status,
        http_time_ms=http_time,
        total_speed_ms=total,
        potential_blocks=blocks,
        restriction_level="none"
    )

    result.potential_blocks = analyze(result) + blocks
    result.restriction_level = get_level(result.potential_blocks)
    return result


# ═══════════════════════════════════════════════════════════════
# Output
# ═══════════════════════════════════════════════════════════════

def print_header():
    print("\n" + "═" * 60)
    print(f"{E.GLOBE}  INTERNET CONNECTION & SPEED CHECKER  {E.DET}")
    print("═" * 60)
    print(f"  {E.DEV} Programmer: {__author__}")
    print(f"  {E.LINK} Website: {__website__}")
    print("═" * 60)


def print_result(r: TestResult):
    print("\n" + "─" * 60)
    print(f"{E.GLOBE}  {r.website.upper()}")
    print("─" * 60)

    # Status
    if r.http_accessible:
        status = f"{E.CHECK} CONNECTED"
    elif r.tls_connected:
        status = f"{E.WARN} PARTIAL"
    else:
        status = f"{E.CROSS} BLOCKED"

    print(f"\n  {E.CHAIN} Status: {status}")

    # Speed
    rating, icon = get_speed(r.total_speed_ms)
    print(f"  {E.SPEED} Speed: {r.total_speed_ms:.0f}ms [{icon} {rating}]")

    # Details
    print(f"\n  {E.DNS} DNS: ", end="")
    print(f"{E.CHECK} {r.dns_ip} ({r.dns_time_ms:.0f}ms)" if r.dns_resolved else f"{E.CROSS} Failed")

    print(f"  {E.CHAIN} TCP: ", end="")
    print(f"{E.CHECK} Connected ({r.tcp_time_ms:.0f}ms)" if r.tcp_connected else f"{E.CROSS} Failed")

    print(f"  {E.LOCK} TLS: ", end="")
    print(f"{E.CHECK} Success ({r.tls_time_ms:.0f}ms)" if r.tls_connected else f"{E.CROSS} Failed")

    print(f"  {E.GLOBE} HTTP: ", end="")
    if r.http_accessible:
        print(f"{E.CHECK} OK [{r.http_status}] ({r.http_time_ms:.0f}ms)")
    else:
        print(f"{E.CROSS} Failed" + (f" [{r.http_status}]" if r.http_status else ""))

    # Restrictions
    if r.potential_blocks:
        print(f"\n  {E.BLOCK} Restrictions Detected:")
        descs = {
            "DNS_BLOCKING": "DNS blocked/poisoned",
            "IP_BLOCKING": "IP address blocked",
            "SNI_FILTERING": "SNI filtering active",
            "TCP_RST": "TCP RST injection",
            "HTTP_FILTERING": "HTTP filtering",
            "GEO_RESTRICTION": "Geo restriction (403)",
            "LEGAL_BLOCKING": "Legal block (451)",
            "MITM_ATTACK": "MITM suspected",
        }
        for b in r.potential_blocks:
            print(f"      {E.WARN} {b}: {descs.get(b, b)}")
    else:
        print(f"\n  {E.CHECK} No restrictions detected")

    # Level
    if r.restriction_level == "none":
        print(f"\n  {E.UNLOCK} Level: {E.CHECK} NONE - Full access")
    elif r.restriction_level == "partial":
        print(f"\n  {E.WARN} Level: {E.WARN} PARTIAL")
    else:
        print(f"\n  {E.BLOCK} Level: {E.CROSS} FULL - Blocked")


def clean_url(url: str) -> str:
    """Extract hostname from URL."""
    url = url.strip()
    url = url.replace("https://", "").replace("http://", "")
    url = url.split("/")[0]
    url = url.split(":")[0]
    return url


# ═══════════════════════════════════════════════════════════════
# Main
# ═══════════════════════════════════════════════════════════════

def main():
    print_header()

    print(f"\n{E.INPUT} Enter website URL to check:")
    print(f"  (Example: google.com, https://github.com)")

    try:
        url = input(f"\n  {E.GLOBE} URL: ")
    except KeyboardInterrupt:
        print(f"\n\n{E.WARN} Cancelled.")
        return

    hostname = clean_url(url)

    if not hostname:
        print(f"\n{E.CROSS} Invalid URL!")
        return

    print(f"\n{E.CLOCK} Checking {hostname}...")

    result = check_website(hostname)
    print_result(result)

    # OpenSSL test
    print(f"\n{E.DET} OpenSSL verification...")
    success, status = test_openssl(hostname)
    icon = E.CHECK if success else E.CROSS
    print(f"  {icon} openssl s_client: {status}")

    # Footer
    print("\n" + "═" * 60)
    print(f"  {E.DEV} Programmer: {__author__}")
    print(f"  {E.LINK} Website: {__website__}")
    print("═" * 60 + "\n")


if __name__ == "__main__":
    main()

