"""Launch SOCKS5 proxy via PowerShell to bypass CSCA process blocking.

CSCA blocks outbound connections from python.exe but trusts system
processes like powershell.exe. This module runs a minimal SOCKS5 proxy
entirely in PowerShell/.NET, so all outbound connections originate from
powershell.exe which CSCA does not block.

Flow:
    python.exe (blocked) → 127.0.0.1:11082 (loopback, not filtered)
    powershell.exe (trusted) → api.deepseek.com:443 (internet)
"""

from __future__ import annotations

import logging
import subprocess
import sys
import time

log = logging.getLogger(__name__)

# PowerShell script that implements a minimal SOCKS5 proxy using .NET sockets.
# Supports CONNECT command only (sufficient for HTTPS API calls).
_PS_SOCKS5_SCRIPT = r'''
$port = if ($args.Count -gt 0) { [int]$args[0] } else { 11082 }
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, $port)
$listener.Start(32)
Write-Host "SOCKS5_READY:$port"

function Relay($a, $b) {
    $buf = New-Object byte[] 8192
    try {
        while ($a.Connected -and $b.Connected) {
            if ($a.GetStream().DataAvailable) {
                $n = $a.GetStream().Read($buf, 0, 8192)
                if ($n -le 0) { break }
                $b.GetStream().Write($buf, 0, $n)
                $b.GetStream().Flush()
            }
            if ($b.GetStream().DataAvailable) {
                $n = $b.GetStream().Read($buf, 0, 8192)
                if ($n -le 0) { break }
                $a.GetStream().Write($buf, 0, $n)
                $a.GetStream().Flush()
            }
            if (-not $a.GetStream().DataAvailable -and -not $b.GetStream().DataAvailable) {
                Start-Sleep -Milliseconds 10
            }
        }
    } catch {}
    try { $a.Close() } catch {}
    try { $b.Close() } catch {}
}

function Handle($client) {
    try {
        $s = $client.GetStream()
        $s.ReadTimeout = 15000
        $s.WriteTimeout = 15000

        # 1. SOCKS5 greeting
        $greet = New-Object byte[] 256
        $n = $s.Read($greet, 0, 256)
        if ($n -lt 3 -or $greet[0] -ne 5) { $client.Close(); return }

        # Reply: no auth required
        $s.Write([byte[]](5, 0), 0, 2)

        # 2. Connection request
        $req = New-Object byte[] 256
        $n = $s.Read($req, 0, 256)
        if ($n -lt 7 -or $req[0] -ne 5 -or $req[1] -ne 1) { $client.Close(); return }

        $atyp = $req[3]
        $addr = ""
        $dport = 0

        if ($atyp -eq 1) {
            # IPv4
            $addr = "$($req[4]).$($req[5]).$($req[6]).$($req[7])"
            $dport = $req[8] * 256 + $req[9]
        } elseif ($atyp -eq 3) {
            # Domain
            $dlen = $req[4]
            $addr = [System.Text.Encoding]::ASCII.GetString($req, 5, $dlen)
            $dport = $req[5 + $dlen] * 256 + $req[6 + $dlen]
        } elseif ($atyp -eq 4) {
            # IPv6
            $ipbytes = New-Object byte[] 16
            [Array]::Copy($req, 4, $ipbytes, 0, 16)
            $ipaddr = [System.Net.IPAddress]::new($ipbytes)
            $addr = $ipaddr.ToString()
            $dport = $req[20] * 256 + $req[21]
        } else {
            $client.Close(); return
        }

        # 3. Connect to destination
        $remote = $null
        try {
            $remote = [System.Net.Sockets.TcpClient]::new()
            $remote.SendTimeout = 15000
            $remote.ReceiveTimeout = 15000
            $remote.Connect($addr, $dport)
        } catch {
            # Connection failed reply
            $fail = [byte[]](5, 4, 0, 1, 0, 0, 0, 0, 0, 0)
            try { $s.Write($fail, 0, 10) } catch {}
            $client.Close()
            return
        }

        # 4. Success reply
        $ok = [byte[]](5, 0, 0, 1, 127, 0, 0, 1, 0, 0)
        $lport = ($remote.Client.LocalEndPoint -as [System.Net.IPEndPoint]).Port
        $ok[8] = [byte]([math]::Floor($lport / 256))
        $ok[9] = [byte]($lport % 256)
        $s.Write($ok, 0, 10)

        # 5. Relay data
        Relay $client $remote
    } catch {
        try { $client.Close() } catch {}
    }
}

while ($true) {
    try {
        if ($listener.Pending()) {
            $client = $listener.AcceptTcpClient()
            $client.SendTimeout = 15000
            $client.ReceiveTimeout = 15000
            $job = [System.Threading.ThreadPool]::QueueUserWorkItem(
                [System.Threading.WaitCallback]{ param($state) Handle $state },
                $client
            )
        } else {
            Start-Sleep -Milliseconds 50
        }
    } catch {
        break
    }
}
'''


class PowerShellSocks5:
    """SOCKS5 proxy running inside PowerShell process.

    PowerShell is a trusted Windows system process that CSCA does not block.
    """

    def __init__(self, port: int = 11082) -> None:
        self.port = port
        self._process: subprocess.Popen | None = None

    @property
    def proxy_url(self) -> str:
        return f"socks5://127.0.0.1:{self.port}"

    @property
    def is_running(self) -> bool:
        return self._process is not None and self._process.poll() is None

    def start(self) -> bool:
        """Start SOCKS5 proxy inside powershell.exe."""
        if sys.platform != "win32":
            log.debug("PowerShellSocks5 only needed on Windows")
            return False

        try:
            creation_flags = 0
            if hasattr(subprocess, "CREATE_NO_WINDOW"):
                creation_flags = subprocess.CREATE_NO_WINDOW

            self._process = subprocess.Popen(
                [
                    "powershell.exe",
                    "-NoProfile",
                    "-NonInteractive",
                    "-ExecutionPolicy", "Bypass",
                    "-Command", _PS_SOCKS5_SCRIPT.replace("$port = if ($args.Count -gt 0) { [int]$args[0] } else { 11082 }",
                                                           f"$port = {self.port}"),
                ],
                stdout=subprocess.PIPE,
                stderr=subprocess.DEVNULL,
                creationflags=creation_flags,
            )

            # Wait for "SOCKS5_READY" signal
            ready = False
            for _ in range(100):  # 10 seconds max
                if self._process.poll() is not None:
                    log.error("PowerShellSocks5 exited prematurely (code=%s)",
                              self._process.returncode)
                    break
                line = b""
                try:
                    line = self._process.stdout.readline()
                except Exception:
                    pass
                if b"SOCKS5_READY" in line:
                    ready = True
                    break
                time.sleep(0.1)

            if ready:
                log.info("PowerShellSocks5 started: %s (pid=%d)",
                         self.proxy_url, self._process.pid)
                return True
            else:
                log.error("PowerShellSocks5 failed to start")
                self.stop()
                return False

        except Exception as exc:
            log.error("PowerShellSocks5 start failed: %s", exc)
            self.stop()
            return False

    def stop(self) -> None:
        """Stop the PowerShell subprocess."""
        if self._process:
            try:
                self._process.terminate()
                self._process.wait(timeout=3)
            except Exception:
                try:
                    self._process.kill()
                except Exception:
                    pass
            self._process = None

    def __del__(self) -> None:
        self.stop()
