import asyncio
import aiohttp
import re
import time
import random
from fake_useragent import UserAgent
from collections import deque

from python_socks import ProxyType
from aiohttp_socks import ProxyConnector
from stem import Signal
from stem.control import Controller
import urllib3
from datetime import datetime
import subprocess
import os
import tempfile

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# ──────────────────────────────────────────────
# CONFIG
# ──────────────────────────────────────────────

POLL_ID = "16789161"
NONCE_HASH = "8440142611c43ae0960bfc4070fc4517"
ANSWER_ID = "73525103"

URL_PARAM = "https://cdn.iframe.ly/api/iframe?url=https%3A%2F%2Fpoll.fm%2F16789161&key=19eb74074f5ecbc09ceba1df9c25110f&v=1&app=1"

# Tor configuration
TOR_PROXY_HOST = "127.0.0.1"
TOR_BASE_SOCKS_PORT = 9050
TOR_BASE_CONTROL_PORT = 9051

IP_CHECK_URL = "http://httpbin.org/ip"

MAX_VOTES_PER_MINUTE = 100  # Reduced to avoid rate limiting
MAX_VOTES_PER_HOUR = 3000
VOTES_BEFORE_RENEW = 3  # Renew more frequently
STANDINGS_EVERY_N_VOTES = 5

CONCURRENT_TASKS = 5  # Reduced to avoid overwhelming Tor

# ──────────────────────────────────────────────
# STATE (Shared across workers)
# ──────────────────────────────────────────────

ua = UserAgent()
minute_votes: deque = deque()
hour_votes: deque = deque()
total_votes = 0
successful_votes = 0
failed_votes = 0
state_lock = asyncio.Lock()

# Track Tor processes and temp files
tor_processes = []
tor_temp_dirs = []


# ──────────────────────────────────────────────
# TOR INSTANCE MANAGEMENT
# ──────────────────────────────────────────────

def start_tor_instance(worker_id: int, socks_port: int, control_port: int):
    """Start a new Tor instance for a worker"""
    try:
        # Create temporary directory for this instance
        temp_dir = tempfile.mkdtemp(prefix=f"tor_worker_{worker_id}_")
        tor_temp_dirs.append(temp_dir)

        # Create torrc for this instance
        torrc_content = f"""SOCKSPort {socks_port}
ControlPort {control_port}
DataDirectory {temp_dir}
CookieAuthentication 0
ExitNodes {{us}}
StrictNodes 1
"""

        torrc_path = os.path.join(temp_dir, "torrc")
        with open(torrc_path, 'w') as f:
            f.write(torrc_content)

        # Start Tor process
        process = subprocess.Popen(
            ['tor', '-f', torrc_path],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            start_new_session=True
        )

        # Wait for Tor to start
        time.sleep(3)

        return process
    except Exception as e:
        print(f"Failed to start Tor instance for worker {worker_id}: {e}")
        return None


def stop_tor_instance(process, temp_dir):
    """Stop a Tor instance and cleanup"""
    try:
        if process:
            process.terminate()
            try:
                process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                process.kill()

        # Cleanup temp directory
        if temp_dir and os.path.exists(temp_dir):
            import shutil
            shutil.rmtree(temp_dir, ignore_errors=True)
    except Exception as e:
        print(f"Error stopping Tor instance: {e}")


async def renew_tor_circuit(worker_id: int, control_port: int):
    """Renew Tor circuit for a specific worker"""
    try:
        loop = asyncio.get_running_loop()
        await loop.run_in_executor(None, _renew_tor_circuit_sync, control_port, worker_id)
        print(f"🔄 Worker {worker_id}: Tor circuit renewed")
        # Give it time to establish new circuit
        await asyncio.sleep(2)
        return True
    except Exception as e:
        print(f"Worker {worker_id}: Tor renewal failed: {e}")
        return False


def _renew_tor_circuit_sync(control_port: int, worker_id: int):
    """Synchronous Tor circuit renewal for specific instance"""
    try:
        with Controller.from_port(port=control_port) as controller:
            controller.authenticate()
            controller.signal(Signal.NEWNYM)
            time.sleep(random.uniform(1.5, 2.5))
    except Exception as e:
        print(f"Worker {worker_id}: Error renewing circuit: {e}")


async def get_current_tor_ip(session, worker_id: int, retries=3):
    """Get current Tor exit IP with retries"""
    for attempt in range(retries):
        try:
            async with session.get(IP_CHECK_URL, timeout=aiohttp.ClientTimeout(total=8)) as r:
                if r.status == 200:
                    data = await r.json()
                    ip = data.get("origin", "unknown").strip()
                    if ip and ip != "unknown":
                        return ip
        except Exception:
            pass

        if attempt < retries - 1:
            await asyncio.sleep(1)

    return "unknown"


def get_headers():
    return {
        "User-Agent": ua.random,
        "Accept": "*/*",
        "Accept-Language": "en-GB,en;q=0.9",
        "Referer": "https://cdn.iframe.ly/",
        "Sec-GPC": "1",
    }


async def fetch_nonce(session):
    """Fetch nonce for voting with retry"""
    for attempt in range(3):
        try:
            ts = int(time.time() * 1000)
            url = f"https://poll.fm/n/{NONCE_HASH}/{POLL_ID}?{ts}"
            async with session.get(url, headers=get_headers(), timeout=aiohttp.ClientTimeout(total=10)) as r:
                if r.status == 200:
                    text = await r.text()
                    m = re.search(r"PDV_n\d+='([^']+)'", text) or re.search(r"n=([a-f0-9]+\|\d+)", text)
                    if m:
                        return m.group(1)
        except Exception as e:
            if attempt == 2:  # Last attempt
                print(f"Nonce error after retries: {e}")
            await asyncio.sleep(1)

    return None


async def cast_vote(session, nonce):
    """Cast a single vote with retry"""
    url = f"https://polls.polldaddy.com/vote-js.php?p={POLL_ID}&b=1&a={ANSWER_ID},&o=&va=16&cookie=0&tags={POLL_ID}-src:poll-oembed-simple&n={nonce}&url={URL_PARAM}"

    for attempt in range(2):
        try:
            async with session.get(url, headers=get_headers(), timeout=aiohttp.ClientTimeout(total=10)) as r:
                if r.status == 200:
                    text = await r.text()
                    if "success" in text.lower() or "thank" in text.lower():
                        return True
        except Exception:
            if attempt == 1:  # Last attempt
                return False
            await asyncio.sleep(0.5)

    return False


async def get_current_difference(session):
    """Parse current vote counts from poll results"""
    try:
        async with session.get(f"https://poll.fm/{POLL_ID}/results",
                               headers=get_headers(),
                               timeout=aiohttp.ClientTimeout(total=10)) as r:
            if r.status != 200:
                return None, None, None

            html = await r.text()
            pattern = re.compile(
                r'<span class="pds-answer-text"[^>]*>\s*(.*?)\s*</span>.*?'
                r'<span class="pds-feedback-votes">\s*&nbsp;\s*\(([\d,]+)\s+votes?\)</span>',
                re.IGNORECASE | re.DOTALL,
            )

            results = {}
            for name, votes_str in pattern.findall(html):
                clean_name = re.sub(r"\s+", " ", name).strip().lower()
                results[clean_name] = int(votes_str.replace(",", ""))

            vakkaru = results.get("vakkaru maldives")
            domes = results.get("domes zeen chania")

            if vakkaru is None or domes is None:
                return None, None, None

            return vakkaru, domes, vakkaru - domes
    except Exception as e:
        print(f"Results parse error: {e}")
        return None, None, None


async def can_vote_now():
    """Check global rate limits"""
    async with state_lock:
        now = time.time()
        while minute_votes and now - minute_votes[0] > 60:
            minute_votes.popleft()
        while hour_votes and now - hour_votes[0] > 3600:
            hour_votes.popleft()

        if len(minute_votes) >= MAX_VOTES_PER_MINUTE:
            wait = 60 - (now - minute_votes[0]) + random.uniform(1, 2)
            return False, wait
        if len(hour_votes) >= MAX_VOTES_PER_HOUR:
            wait = 3600 - (now - hour_votes[0]) + random.uniform(10, 20)
            return False, wait
        return True, 0


async def add_vote_record(success: bool):
    """Record a vote attempt globally"""
    async with state_lock:
        global total_votes, successful_votes, failed_votes
        total_votes += 1
        if success:
            successful_votes += 1
            now = time.time()
            minute_votes.append(now)
            hour_votes.append(now)
        else:
            failed_votes += 1


async def print_instant_stats(worker_id: int, success: bool, ip: str, vote_num: int, vakkaru=None, domes=None,
                              diff=None):
    """Print instant feedback after each vote"""
    async with state_lock:
        timestamp = datetime.now().strftime("%H:%M:%S")
        status = "✅" if success else "❌"
        vote_status = "ACCEPTED" if success else "FAILED"

        base = f"[{timestamp}] {status} W{worker_id:2d} | IP: {ip:15s} | #{vote_num} | {vote_status}"

        if vakkaru is not None and domes is not None:
            if vakkaru < domes:
                lead_status = "🔴 BEHIND"
            elif vakkaru > domes:
                lead_status = "🟢 AHEAD"
            else:
                lead_status = "⚖️ TIED"

            print(f"{base} | {lead_status} | V: {vakkaru:,} vs D: {domes:,} | Diff: {diff:+d} | "
                  f"Total: {total_votes:,} (✅{successful_votes} ❌{failed_votes})")
        else:
            print(f"{base} | Total: {total_votes:,} (✅{successful_votes} ❌{failed_votes})")


async def vote_worker(worker_id: int):
    """Individual worker with its OWN Tor instance and IP"""

    # Each worker gets its own ports
    socks_port = TOR_BASE_SOCKS_PORT + worker_id
    control_port = TOR_BASE_CONTROL_PORT + worker_id

    # Start Tor instance for this worker
    print(f"🚀 Starting Tor instance for worker {worker_id} on port {socks_port}")
    tor_process = start_tor_instance(worker_id, socks_port, control_port)

    if not tor_process:
        print(f"❌ Worker {worker_id} failed to start Tor, skipping...")
        return

    tor_processes.append((tor_process, tor_temp_dirs[-1] if tor_temp_dirs else None))

    # Create connector with worker's specific Tor port
    connector = ProxyConnector(
        proxy_type=ProxyType.SOCKS5,
        host=TOR_PROXY_HOST,
        port=socks_port,
        rdns=True,
    )
    timeout = aiohttp.ClientTimeout(total=15, sock_connect=10, sock_read=10)

    async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
        personal_votes = 0
        consecutive_fails = 0

        # Get initial IP
        await asyncio.sleep(2)
        current_ip = await get_current_tor_ip(session, worker_id)
        print(f"✅ Worker {worker_id} ready | IP: {current_ip} | Port: {socks_port}")

        while True:
            try:
                # Check global rate limits
                can, wait_time = await can_vote_now()
                if not can:
                    await asyncio.sleep(wait_time)
                    continue

                # Add small delay between votes to prevent flooding
                await asyncio.sleep(random.uniform(0.5, 1.0))

                # Fetch nonce
                nonce = await fetch_nonce(session)
                if not nonce:
                    await asyncio.sleep(random.uniform(1, 2))
                    continue

                # Cast vote
                success = await cast_vote(session, nonce)

                # Record vote
                await add_vote_record(success)
                personal_votes += 1

                if success:
                    consecutive_fails = 0

                    # Renew circuit after personal vote count
                    if personal_votes % VOTES_BEFORE_RENEW == 0:
                        await renew_tor_circuit(worker_id, control_port)
                        # Get new IP after renewal
                        current_ip = await get_current_tor_ip(session, worker_id)

                    # Check standings occasionally
                    show_standings = (personal_votes % STANDINGS_EVERY_N_VOTES == 0)
                    vakkaru = domes = diff = None

                    if show_standings:
                        vakkaru, domes, diff = await get_current_difference(session)

                    # Print stats
                    await print_instant_stats(worker_id, success, current_ip, personal_votes, vakkaru, domes, diff)

                    # Determine delay
                    if vakkaru is not None and domes is not None and vakkaru < domes:
                        delay = random.uniform(0.8, 1.5)
                        print(f"  🔥 W{worker_id} CATCHING UP! ({delay:.1f}s)")
                    else:
                        delay = random.uniform(1.5, 3.0)

                    await asyncio.sleep(delay)
                else:
                    consecutive_fails += 1
                    await print_instant_stats(worker_id, success, current_ip, personal_votes)

                    # Exponential backoff on failures
                    backoff = min(20, 3 * consecutive_fails)
                    await asyncio.sleep(random.uniform(backoff, backoff + 2))

            except asyncio.CancelledError:
                break
            except Exception as e:
                print(f"Worker {worker_id} error: {e}")
                await asyncio.sleep(3)


async def main():
    """Main async entry point"""
    print(f"🚀 Starting async vote bot with {CONCURRENT_TASKS} workers")
    print(f"📊 Target: ANSWER_ID={ANSWER_ID}")
    print(f"⚙️  Each worker gets its own Tor instance on different ports")
    print(f"⚙️  Workers: ports {TOR_BASE_SOCKS_PORT}-{TOR_BASE_SOCKS_PORT + CONCURRENT_TASKS - 1}")
    print(f"⚙️  Max {MAX_VOTES_PER_MINUTE} votes/min globally")
    print("-" * 80)

    try:
        # Start all workers
        workers = [vote_worker(i) for i in range(CONCURRENT_TASKS)]
        await asyncio.gather(*workers)
    except KeyboardInterrupt:
        print("\n\n🛑 Stopping bot...")
    finally:
        # Clean up Tor processes
        print("🧹 Cleaning up Tor instances...")
        for process, temp_dir in tor_processes:
            stop_tor_instance(process, temp_dir)

        async with state_lock:
            print(f"\n📊 FINAL STATS:")
            print(f"   Total votes: {total_votes:,}")
            print(f"   Successful : {successful_votes:,}")
            print(f"   Failed     : {failed_votes:,}")
            if total_votes > 0:
                print(f"   Success rate: {(successful_votes / total_votes * 100):.1f}%")


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n✅ Bot stopped")
