import json
import os
import re
import time
import asyncio
import random
import traceback
from concurrent.futures import ThreadPoolExecutor
import logging
from urllib.parse import urljoin

from selenium.common.exceptions import NoSuchElementException, TimeoutException, ElementClickInterceptedException, \
    WebDriverException
import undetected_chromedriver as uc
from faker import Faker
from pydantic import ValidationError
from selenium.common import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

import aiomysql
from dotenv import load_dotenv
import state
from parser_logger import ParserLogger
from schemas import ParserStatusEnum, ParsedProductSchema
from proxy_manager import ProxyManager

load_dotenv()
faker = Faker()
proxy_manager = None

logger = logging.getLogger("parser")

parse_attempts = 0
parse_successes = 0
parse_failures = 0
proxy_bans = 0

executor = ThreadPoolExecutor(max_workers=1)
IS_LOCAL = os.getenv("IS_LOCAL", "false").lower() == "true"

def create_driver(proxy_info=None, headless=True):
    fake_user_agent = faker.user_agent()
    fake_lang = faker.language_code()

    options = uc.ChromeOptions()
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.add_argument("--disable-dev-shm-usage")
    options.add_argument("--disable-extensions")
    options.add_argument("start-maximized")
    options.add_argument(f"--user-agent={fake_user_agent}")
    options.add_argument(f"--lang={fake_lang}")

    if headless:
        options.add_argument("--headless")  # або просто "--headless"

    ip = proxy_info.get("proxy")
    port = proxy_info.get("port")
    proxy_auth = f"http://{ip}:{port}"

    print("Прокси: " + proxy_auth)

    options.add_argument(f"--proxy-server={proxy_auth}")

    try:
        driver = uc.Chrome(options=options)
    except Exception as e:
        print(f"Ошибка запуска веб драйвера {e}")
        traceback.print_exc()
    print("Веб Драйвер запущен")
    driver.set_page_load_timeout(30)

    # Anti-detect патчі (залишаємо як є)
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": """
        Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
        Object.defineProperty(navigator, 'languages', { get: () => ['uk-UA', 'uk', 'en-US', 'en'] });
        Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
        Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 });
        Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
        Object.defineProperty(window, 'screen', {
          get: () => ({
            width: 1920,
            height: 1080,
            availWidth: 1920,
            availHeight: 1040,
            colorDepth: 24,
            pixelDepth: 24
          })
        });
        HTMLCanvasElement.prototype.toDataURL = function() {
          return "data:image/png;base64,fakebase64canvas==";
        };
        const getParameter = WebGLRenderingContext.prototype.getParameter;
        WebGLRenderingContext.prototype.getParameter = function(param) {
          if (param === 37445) return 'Intel Inc.';
          if (param === 37446) return 'Intel Iris OpenGL Engine';
          return getParameter(param);
        };
        const originalGetFloatFrequencyData = AnalyserNode.prototype.getFloatFrequencyData;
        AnalyserNode.prototype.getFloatFrequencyData = function(array) {
          for (let i = 0; i < array.length; i++) array[i] = 100;
        };
        Object.defineProperty(window, 'RTCPeerConnection', { get: () => undefined });
        """
    })

    # driver.set_window_size(1920, 1080)

    print("Проверяю прокси")
    try:
        driver.get("https://api.2ip.ua/geo.json?ip=")
        start = driver.page_source.find("<pre>") + len("<pre>")
        end = driver.page_source.find("</pre>")
        json_data = driver.page_source[start:end]

        ip = json.loads(json_data)
        print(ip['ip'])
    except Exception as e:
        print(f"Ошибка проверки прокси: {e}")

    return driver


def create_and_prepare_driver(proxy_info=None, headless=True):
    driver = create_driver(proxy_info=proxy_info, headless=headless)
    driver.get("https://hotline.ua/")
    time.sleep(random.uniform(3, 6))  # Прогрів сесії
    return driver


def human_like_interaction(driver):
    wait = WebDriverWait(driver, 10)
    close_popup_if_present(driver)
    height = driver.execute_script("return document.body.scrollHeight")

    time.sleep(random.uniform(5, 8))

    try:
        images = driver.find_elements(By.CSS_SELECTOR, "div.zoom-gallery__nav img")
        if images and random.random() < 0.7:
            click_count = min(len(images), random.randint(1, 2))
            images_to_click = random.sample(images, click_count)

            driver.execute_script("""
                let iframes = document.querySelectorAll('iframe');
                iframes.forEach(f => {
                    f.style.pointerEvents = 'none';
                    f.style.visibility = 'hidden';
                });
            """)

            for img in images_to_click:
                driver.execute_script("arguments[0].scrollIntoView({behavior: 'auto', block: 'center'});", img)
                time.sleep(random.uniform(1.2, 2.0))
                try:
                    wait.until(EC.element_to_be_clickable(img))
                    if random.random() < 0.85:
                        img.click()
                    else:
                        driver.execute_script("arguments[0].click();", img)
                except (TimeoutException, ElementClickInterceptedException, WebDriverException) as e:
                    logger.debug(f"Не вдалося клікнути по зображенню (ігноруємо): {e}")
                time.sleep(random.uniform(1.0, 2.5))
        else:
            time.sleep(random.uniform(2, 4))
    except Exception as e:
        logger.debug(f"Не вдалося клікнути по зображеннях (ігноруємо): {e}")

    scroll_percent = random.uniform(0.2, 0.4)
    scroll_px = height * scroll_percent
    steps = random.randint(8, 15)

    for i in range(steps):
        move = scroll_px / steps * random.uniform(0.7, 1.3)
        driver.execute_script(f"window.scrollBy(0, {move});")
        time.sleep(random.uniform(0.25, 0.5))

    time.sleep(random.uniform(1.5, 2.5))

    for i in range(steps):
        move = scroll_px / steps * random.uniform(0.7, 1.3)
        driver.execute_script(f"window.scrollBy(0, {-move});")
        time.sleep(random.uniform(0.25, 0.5))

    time.sleep(random.uniform(1, 2))


def close_popup_if_present(driver):
    try:
        popup = driver.find_element(By.CSS_SELECTOR, ".grv-unblock-label-host")
        if popup.is_displayed():
            driver.execute_script("arguments[0].style.display = 'none';", popup)
            logger.info("🧹 Закрили спливаюче вікно (.grv-unblock-label-host)")
            time.sleep(1)
    except NoSuchElementException:
        pass
    except Exception as e:
        logger.warning(f"⚠️ Помилка при закритті .grv-unblock-label-host: {e}")

    try:
        overlay = driver.find_element(By.CSS_SELECTOR, "div.modal-overlay")
        if overlay.is_displayed():
            driver.execute_script("arguments[0].style.display = 'none';", overlay)
            logger.info("🧹 Закрили модальне вікно (modal-overlay)")
            time.sleep(1.5)
    except NoSuchElementException:
        pass
    except Exception as e:
        logger.warning(f"⚠️ Помилка при закритті modal-overlay: {e}")
    finally:
        driver.execute_script("document.body.style.zoom='80%'")

def scroll_characteristics_container(driver):
    """
    Плавно скролить контейнер з характеристиками, щоб підвантажити всі.
    Збирає унікальні характеристики, уникаючи дублікатів.
    """
    characteristics_dict = {}
    current_group = "Технічні характеристики"
    seen = set()

    try:
        container = driver.find_element(By.CSS_SELECTOR,
            "div.about__info div.specifications")
        container_height = driver.execute_script("return arguments[0].scrollHeight", container)

        scroll_top = 0
        step = 100
        max_steps = 25

        for _ in range(max_steps):
            driver.execute_script("arguments[0].scrollTop = arguments[1];", container, scroll_top)
            time.sleep(0.2)

            rows = container.find_elements(By.CSS_SELECTOR, "table.specifications-table tbody tr")

            for i, row in enumerate(rows):
                try:
                    cells = row.find_elements(By.TAG_NAME, "td")
                    if len(cells) == 1:
                        colspan = cells[0].get_attribute("colspan")
                        if colspan == "2":
                            h3 = cells[0].find_element(By.TAG_NAME, "h3")
                            current_group = h3.text.strip()
                            continue

                    if len(cells) >= 2:
                        label = cells[0].text.strip().replace("\n", "").replace("?", "").strip(" :\t")
                        links = cells[1].find_elements(By.TAG_NAME, "a")
                        if links:
                            value = links[0].text.strip()
                        else:
                            value = cells[1].text.strip().replace("\n", "").strip(" :\t")

                        key = (current_group, label)
                        if key not in seen and (label or value):
                            seen.add(key)
                            characteristics_dict.setdefault(current_group, {})[label] = value
                except Exception as e:
                    logger.warning(f"⚠️ Не вдалося обробити рядок {i}: {e}")

            scroll_top += step
            if scroll_top > container_height:
                break

    except Exception as e:
        logger.error(f"❌ Помилка при скролі характеристик: {e}", exc_info=True)

    # Формуємо список
    seen_filters = set()
    characteristics_list = []
    for group, attrs in characteristics_dict.items():
        for filter_name, value in attrs.items():
            if filter_name not in seen_filters:
                seen_filters.add(filter_name)
                characteristics_list.append({
                    "group": group,
                    "filter_name": filter_name,
                    "value": value
                })

    return characteristics_list

def parse_product(driver, url):
    driver.get(url)
    wait = WebDriverWait(driver, 17)
    driver.execute_script("document.body.style.zoom='80%'")

    # Міні-скролл вниз-вгору для активації
    driver.execute_script("window.scrollTo(0, 0);")
    time.sleep(0.5)
    driver.execute_script("window.scrollBy(0, -150);")
    time.sleep(0.5)

    characteristics_list = []

    # Клікаємо кнопку або вкладку і одразу збираємо характеристики
    try:
        wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "div.title.text-x-lg")))
        logger.info("✅ Блок 'Характеристики' завантажений")

        try:
            view_all = driver.find_element(By.CSS_SELECTOR, 'span[data-tracking-id="product-79"].link')
            driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", view_all)
            time.sleep(0.3)
            view_all.click()
            logger.info("🟢 Натиснули 'Дивитися усі'")
            time.sleep(2)
            characteristics_list = scroll_characteristics_container(driver)
        except NoSuchElementException:
            logger.info("ℹ️ 'Дивитися усі' відсутня — пробуємо 'Про товар'")
            try:
                tab = driver.find_element(By.CSS_SELECTOR, 'div[data-tracking-id="product-19"].tabs-item')
                driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", tab)
                time.sleep(0.3)
                tab.click()
                logger.info("🟢 Натиснули 'Про товар'")
                time.sleep(2)
                characteristics_list = scroll_characteristics_container(driver)
            except Exception as e:
                logger.warning(f"❌ Не вдалося клікнути 'Про товар': {e}")
    except TimeoutException:
        logger.warning("⚠️ Блок характеристик не завантажився")

    # Назва повна
    full_name = None
    try:
        full_name = wait.until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "div.header h1.title__main"))
        ).text.strip()
    except TimeoutException:
        logger.error("❌ Повна назва не знайдена (timeout)")

    # Назва коротка
    short_name = None
    try:
        for attempt in range(1):
            try:
                time.sleep(random.uniform(1, 1.5))
                offers_title = wait.until(
                    EC.visibility_of_element_located((By.CSS_SELECTOR, "div.offers-list__title"))
                )
                short_name = offers_title.text.replace("Де купити", "").strip()
                logger.info(f"✅ Коротка назва: {short_name}")
                break
            except TimeoutException:
                logger.warning(f"⚠️ offers-list__title не зʼявився (спроба {attempt + 1})")
    except Exception as e:
        logger.error(f"❌ Помилка при парсингу short_name: {e}")

    # Імітація активності
    human_like_interaction(driver)

    # Опис товару
    product_description = None
    try:
        # 1. Пробуємо клікнути кнопку "розгорнути", якщо вона є
        try:
            expand_button = driver.find_element(By.CSS_SELECTOR, "button.expand-button")
            driver.execute_script("arguments[0].click();", expand_button)
            time.sleep(0.5)
            logger.info("📖 Натиснули кнопку 'розгорнути'")
        except NoSuchElementException:
            logger.info("ℹ️ Кнопка 'розгорнути' відсутня")

        # 2. Тепер пробуємо дістати повний опис
        desc_div = driver.find_element(By.CSS_SELECTOR, "div.description__content div.text-wrapper div")
        product_description = desc_div.text.strip()

    except NoSuchElementException:
        logger.warning("❌ Опис товару не знайдено")
    except Exception as e:
        logger.error(f"⚠️ Помилка при парсингу опису: {e}")

    # Попапи
    close_popup_if_present(driver)

    # Зображення
    images = []

    try:
        scripts = driver.find_elements(By.TAG_NAME, "script")

        # 1. Пошук imageLinks (галерея)
        script_text = next(
            (s.get_attribute("innerHTML") for s in scripts if "imageLinks" in s.get_attribute("innerHTML")),
            None
        )

        if script_text:
            pattern = re.compile(r'big:"(\\u002F[^"]+)"')
            matches = pattern.findall(script_text)

            def decode_unicode_escapes(s): return s.encode().decode('unicode_escape')

            base_url = "https://hotline.ua"
            big_urls = [decode_unicode_escapes(m) for m in matches]
            images = [base_url + url for url in big_urls]
            logger.info(f"🖼️ Знайдено {len(images)} зображень у imageLinks")

        # 2. Якщо немає — шукаємо поле "image:" з головним зображенням
        if not images:
            for s in scripts:
                text = s.get_attribute("innerHTML")

                # Спочатку пробуємо знайти саме big-зображення
                match_big = re.search(r'\bbig\s*:\s*"([^"]+)"', text)
                if match_big:
                    img_url = match_big.group(1).encode().decode("unicode_escape")
                    if img_url.startswith("/"):
                        img_url = "https://hotline.ua" + img_url
                    images = [img_url]
                    logger.info("🖼️ Знайдено головне зображення через поле big:")
                    break

                # Якщо big немає, шукаємо поле image
                match_image = re.search(r'\bimage\s*:\s*"([^"]+)"', text)
                if match_image:
                    img_url = match_image.group(1).encode().decode("unicode_escape")
                    if img_url.startswith("/"):
                        img_url = "https://hotline.ua" + img_url
                    images = [img_url]
                    logger.info("🖼️ Знайдено головне зображення через поле image:")
                    break



        if not images:
            logger.warning("❌ Зображення не знайдено в жодному скрипті")

    except Exception as e:
        logger.error(f"❌ Помилка при парсингу зображень: {e}", exc_info=True)

    return {
        "full_name": full_name or "",
        "short_name": short_name or "",
        "product_description": product_description,
        "images": images,
        "characteristics": characteristics_list,
    }



async def parse_product_async(url, proxy_info, headless=True):
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(executor, parse_product, url, proxy_info, headless)


MAX_RETRIES = 3


async def parser_loop(proxy_manager_ext: ProxyManager):
    global parse_attempts, parse_successes, parse_failures, proxy_bans

    proxy_manager = proxy_manager_ext
    IS_LOCAL = os.getenv("IS_LOCAL", "false").lower() == "true"
    headless_mode = not IS_LOCAL

    pool = proxy_manager.db_pool
    parser_logger = ParserLogger(pool)

    await proxy_manager.load_proxies()
    asyncio.create_task(proxy_manager.run_periodic_unban())

    driver = None
    current_proxy = None

    while True:
        state.parser_status = ParserStatusEnum.idle
        await parser_logger.log("waiting", "Очікуємо нові URL")

        async with pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cursor:
                await cursor.execute("SELECT * FROM url_queue WHERE status = 0")
                rows = await cursor.fetchall()

                if not rows:
                    if driver:
                        try:
                            driver.quit()
                            logger.info("Драйвер закритий, бо посилання закінчились")
                        except Exception as e:
                            logger.warning(f"Помилка при закритті драйвера: {e}")
                        driver = None
                        current_proxy = None

                    await asyncio.sleep(60)
                    continue

                state.parser_status = ParserStatusEnum.working

                for row in rows:
                    url_id = row["url_id"]
                    url = row["url"]

                    await parser_logger.log("queue", "Взяли URL в обробку", url=url)

                    await cursor.execute(
                        "UPDATE url_queue SET status = %s, date_modify = CURRENT_TIMESTAMP WHERE url_id = %s",
                        (1, url_id)
                    )
                    await conn.commit()

                    success = False

                    for attempt in range(1, MAX_RETRIES + 1):
                        if driver is None or current_proxy is None:
                            current_proxy = await proxy_manager.get_proxy()
                            if not current_proxy:
                                await parser_logger.log("proxy_change", "Немає доступних проксі, очікуємо...")
                                for _ in range(12):
                                    await asyncio.sleep(5)
                                    await proxy_manager.load_proxies()
                                    current_proxy = await proxy_manager.get_proxy()
                                    if current_proxy:
                                        await parser_logger.log("proxy_change", "Знайдено розбанений проксі",
                                                                proxy=current_proxy["username"])
                                        break
                                else:
                                    await parser_logger.log("proxy_change", "❌ Не знайдено доступних проксі", url=url)
                                    break

                            if driver:
                                try:
                                    driver.quit()
                                except Exception:
                                    pass
                                driver = None

                            driver = await asyncio.get_event_loop().run_in_executor(
                                None,
                                create_and_prepare_driver,
                                current_proxy,
                                headless_mode
                            )

                        parse_attempts += 1

                        try:
                            await parser_logger.log("parsing", f"Спроба парсингу {attempt}", url=url,
                                                    proxy=current_proxy["username"])

                            data = await asyncio.get_event_loop().run_in_executor(
                                executor,
                                parse_product,
                                driver,
                                url
                            )

                            ParsedProductSchema(**data)

                            await cursor.execute(
                                "UPDATE url_queue SET status = %s, date_modify = CURRENT_TIMESTAMP, recived = %s, data = %s WHERE url_id = %s",
                                (2, 0, json.dumps(data, ensure_ascii=False), url_id)
                            )
                            await conn.commit()

                            parse_successes += 1
                            await parser_logger.log("idle", "✅ Парсинг успішний", url=url,
                                                    proxy=current_proxy["proxy"],
                                                    success=True)
                            success = True
                            break

                        except ValidationError as ve:
                            parse_failures += 1
                            await parser_logger.log("idle", f"ValidationError: {ve}", url=url,
                                                    proxy=current_proxy["proxy"], success=False)
                            await cursor.execute(
                                "UPDATE url_queue SET status = %s, date_modify = CURRENT_TIMESTAMP, recived = %s, data = %s WHERE url_id = %s",
                                (3, 0, json.dumps({"validation_error": str(ve)}, ensure_ascii=False), url_id)
                            )
                            await conn.commit()
                            break

                        except Exception as e:
                            parse_failures += 1
                            proxy_bans += 1
                            trace = traceback.format_exc()
                            await parser_logger.log(
                                "idle",
                                f"Парсинг не вдався: {e}",
                                url=url,
                                proxy=current_proxy["proxy"],
                                success=False,
                                traceback=trace
                            )
                            await proxy_manager.ban_proxy(current_proxy["username"])

                            if driver:
                                try:
                                    driver.quit()
                                except Exception as e:
                                    logger.warning(f"Помилка при закритті драйвера: {e}\n{traceback.format_exc()}")
                                driver = None
                                current_proxy = None

                            if attempt == MAX_RETRIES:
                                await cursor.execute(
                                    "UPDATE url_queue SET status = %s, date_modify = CURRENT_TIMESTAMP, recived = %s, data = %s WHERE url_id = %s",
                                    (
                                        3, 0, json.dumps({"error": str(e), "traceback": trace}, ensure_ascii=False),
                                        url_id)
                                )
                                await conn.commit()
                                await parser_logger.log("idle", "🚫 Всі спроби вичерпані", url=url,
                                                        proxy=current_proxy["proxy"] if current_proxy else None)

                        await asyncio.sleep(random.uniform(2.5, 6))

        await asyncio.sleep(5)