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

# ──────────────────────────────────────────────
# 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"

PROXIES_FILE = "proxies_scape.txt"

MAX_VOTES_PER_MINUTE = 1000
MAX_VOTES_PER_HOUR = 30000
VOTES_BEFORE_RENEW = 5          # How many votes before forcing a new proxy (optional)
STANDINGS_EVERY_N_VOTES = 5

CONCURRENT_TASKS = 20           # ← Increased as requested

# ──────────────────────────────────────────────
# STATE
# ──────────────────────────────────────────────

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

# Global proxy list (loaded once)
proxies = []


def load_proxies():
    """Load proxies from file"""
    global proxies
    if not os.path.exists(PROXIES_FILE):
        print(f"❌ Error: {PROXIES_FILE} not found in the script directory!")
        exit(1)

    with open(PROXIES_FILE, "r", encoding="utf-8") as f:
        proxies = [line.strip() for line in f if line.strip() and not line.startswith("#")]

    if not proxies:
        print("❌ No proxies found in proxies_scape.txt")
        exit(1)

    print(f"✅ Loaded {len(proxies)} proxies from {PROXIES_FILE}")


def get_random_proxy():
    """Return a random proxy in aiohttp format: http://IP:port"""
    if not proxies:
        return None
    proxy = random.choice(proxies)
    return f"http://{proxy}" if not proxy.startswith("http") else proxy


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 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:
            if attempt == 2:
                print("Nonce fetch failed after retries")
            await asyncio.sleep(1)
    return None


async def cast_vote(session, nonce):
    """Cast 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:
                return False
            await asyncio.sleep(0.5)
    return False


async def get_current_difference(session):
    """Get current vote standings"""
    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():
    """Global rate limiting"""
    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):
    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, proxy: str, vote_num: int, vakkaru=None, domes=None, diff=None):
    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} | Proxy: {proxy[-20:]:20s} | #{vote_num} | {vote_status}"

        if vakkaru is not None and domes is not None:
            lead_status = "🟢 AHEAD" if vakkaru > domes else "🔴 BEHIND" if vakkaru < domes else "⚖️ 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):
    """Worker using random proxy on every vote"""
    personal_votes = 0
    consecutive_fails = 0

    timeout = aiohttp.ClientTimeout(total=15, sock_connect=10, sock_read=10)

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

            # Small random delay
            await asyncio.sleep(random.uniform(0.4, 1.2))

            proxy_url = get_random_proxy()
            if not proxy_url:
                print(f"Worker {worker_id}: No proxies available!")
                await asyncio.sleep(5)
                continue

            # Create a fresh session for each vote (safest with many proxies)
            connector = aiohttp.TCPConnector(ssl=False, limit=0)  # limit=0 disables connection pooling
            async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
                # 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 result
            await add_vote_record(success)
            personal_votes += 1

            if success:
                consecutive_fails = 0

                # Optional: force new proxy more often
                show_standings = (personal_votes % STANDINGS_EVERY_N_VOTES == 0)
                vakkaru = domes = diff = None

                if show_standings:
                    # Quick session just for standings
                    async with aiohttp.ClientSession(timeout=timeout) as temp_session:
                        vakkaru, domes, diff = await get_current_difference(temp_session)

                await print_instant_stats(worker_id, success, proxy_url, personal_votes, vakkaru, domes, diff)

                # Adaptive delay
                if vakkaru is not None and domes is not None and vakkaru < domes:
                    delay = random.uniform(0.6, 1.4)
                    print(f"  🔥 W{worker_id} CATCHING UP! ({delay:.1f}s)")
                else:
                    delay = random.uniform(1.2, 2.8)
                await asyncio.sleep(delay)

            else:
                consecutive_fails += 1
                await print_instant_stats(worker_id, success, proxy_url, personal_votes)

                backoff = min(15, 2 * 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():
    load_proxies()

    print(f"🚀 Starting vote bot with {CONCURRENT_TASKS} concurrent workers")
    print(f"📊 Target: ANSWER_ID={ANSWER_ID}")
    print(f"🔄 Using random HTTP proxies from {PROXIES_FILE} ({len(proxies)} loaded)")
    print(f"⚙️  Global limits: {MAX_VOTES_PER_MINUTE}/min | {MAX_VOTES_PER_HOUR}/hour")
    print("-" * 90)

    try:
        workers = [vote_worker(i) for i in range(CONCURRENT_TASKS)]
        await asyncio.gather(*workers, return_exceptions=True)
    except KeyboardInterrupt:
        print("\n\n🛑 Bot stopped by user...")
    finally:
        async with state_lock:
            print(f"\n📊 FINAL STATS:")
            print(f"   Total attempts : {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__":
    asyncio.run(main())
