"""
Lenh `dsg run` — chay tu dong tao video (ordered hoac greedy).
"""
from __future__ import annotations

import sys
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from math import ceil

import click

from ..config import load_settings, validate_settings
from ..profile_manager import ensure_profile_exists
from ..browser_launcher import launch_chrome_for_automation, close_chrome
from ..sora_bot import reset_page, create_video, VideoGenLimitReachedError
from ..api_client import get_pending_products, mark_product_done
from ..telegram_notifier import (
    notify_product_done,
    notify_product_failed,
    notify_profile_done,
    notify_profile_limit_reached,
    notify_all_done,
)
from ..run_tracker import record_product_done, record_product_failed
from ..session_manager import (
    has_active_session,
    update_product_status,
    mark_session_done,
    clear_session,
    create_session,
    PENDING,
    DONE,
    FAILED,
)
from ..constants import (
    PRODUCTS_PER_PROFILE,
    CDP_PORT_BASE,
)
from .common import _LINE, _BOLD, echo, image_src


def _process_ordered_batch(
    batch_info: dict,
    profile_idx: int,
    settings: dict,
    lock: threading.Lock,
    counters: dict,
    counters_lock: threading.Lock,
) -> None:
    profile_id = batch_info["profile_id"]
    alias = batch_info["alias"]
    pending = [p for p in batch_info["products"] if p["status"] == PENDING]

    if not pending:
        echo(lock, f"\n  [SKIP] {alias}: tat ca san pham da hoan thanh truoc do.")
        return

    echo(lock, f"\n{_LINE}\n  Profile: {alias}  |  Con lai: {len(pending)} san pham\n{_LINE}")

    profile_path = ensure_profile_exists(profile_id)
    debug_port = CDP_PORT_BASE + profile_idx
    proc = pw = None
    p_done = p_failed = 0

    try:
        proc, pw, _browser, _ctx, page = launch_chrome_for_automation(profile_path, debug_port)

        for prod_idx, product in enumerate(pending):
            pid = product["id"]
            pname = product["product_name"]
            prompt = product["prompt_content"]
            image_src_val = image_src(product.get("product_image"))
            try:
                fresh_list = get_pending_products(settings["username"])
                fresh_map = {p["id"]: p for p in fresh_list}
                if pid not in fresh_map:
                    echo(lock, f"\n  [{alias}] [SKIP] {pname}: da duoc xu ly boi tien trinh khac.")
                    update_product_status(profile_id, pid, DONE)
                    continue
                latest = fresh_map[pid]
                pname = latest.get("product_name", pname)
                prompt = latest.get("prompt_content", prompt)
                image_src_val = image_src(
                    latest.get("product_image") or latest.get("image_url")
                )
            except Exception as e:
                echo(lock, f"  [{alias}] Canh bao re-fetch that bai ({e}), dung cache.")

            echo(lock, f"\n  [{alias}] [{prod_idx + 1}/{len(pending)}] {pname}")
            echo(lock, f"  Anh: {image_src_val or '(mac dinh)'}")

            try:
                create_video(page, prompt, image_src_val)
                mark_product_done(pid, settings["backend_key"])
                p_done += 1
                with counters_lock:
                    counters["done"] += 1
                record_product_done(profile_id, pid)
                update_product_status(profile_id, pid, DONE)
                echo(lock, f"  OK  [{alias}] {pname}")
                notify_product_done(settings, alias, pname)
            except VideoGenLimitReachedError:
                p_failed += 1
                with counters_lock:
                    counters["failed"] += 1
                record_product_failed(profile_id)
                update_product_status(profile_id, pid, FAILED)
                echo(lock, f"  [GIOI HAN] [{alias}] {pname}: da tao toi da video trong ngay.")
                notify_profile_limit_reached(settings, alias)
                echo(lock, f"  [GIOI HAN] {alias}: Tat profile, tiep tuc voi profile khac (neu co).")
                break
            except Exception as e:
                p_failed += 1
                with counters_lock:
                    counters["failed"] += 1
                record_product_failed(profile_id)
                update_product_status(profile_id, pid, FAILED)
                echo(lock, f"  LOI  [{alias}] {pname}: {e}")
                notify_product_failed(settings, alias, pname, str(e))

            if prod_idx < len(pending) - 1:
                wait_min = settings.get("wait_between_products_min", 10)
                wait_sec = wait_min * 60
                echo(lock, f"\n  [{alias}] Cho {wait_min} phut de gen video ...")
                time.sleep(wait_sec)
                echo(lock, f"  [{alias}] F5 trang ...")
                reset_page(page)

    except Exception as e:
        echo(lock, f"\n  LOI NGHIEM TRONG ({alias}): {e}")
        with counters_lock:
            counters["failed"] += len(pending) - p_done - p_failed
    finally:
        if pw:
            try:
                pw.stop()
            except Exception:
                pass
        if proc:
            close_chrome(proc)
            echo(lock, f"\n  Da dong Chrome: {alias}")

    echo(lock, f"\n  {alias}: OK {p_done} | LOI {p_failed}")
    notify_profile_done(settings, alias, p_done, p_failed)


def _process_greedy_profile(
    profile: dict,
    profile_idx: int,
    settings: dict,
    claim_lock: threading.Lock,
    claimed: set,
    lock: threading.Lock,
    counters: dict,
    counters_lock: threading.Lock,
) -> None:
    profile_id = profile["id"]
    alias = profile.get("alias", profile_id)
    debug_port = CDP_PORT_BASE + profile_idx

    profile_path = ensure_profile_exists(profile_id)
    proc = pw = None
    p_done = p_failed = 0
    current_pid: int | None = None

    try:
        proc, pw, _browser, _ctx, page = launch_chrome_for_automation(profile_path, debug_port)

        for product_count in range(PRODUCTS_PER_PROFILE):
            product = None
            with claim_lock:
                try:
                    fresh = get_pending_products(settings["username"])
                except Exception as e:
                    echo(lock, f"  [{alias}] Loi fetch API: {e}")
                    break
                available = [p for p in fresh if p["id"] not in claimed]
                if not available:
                    echo(lock, f"  [{alias}] Khong con san pham nao de xu ly.")
                    break
                product = available[0]
                claimed.add(product["id"])

            current_pid = product["id"]
            pname = product["product_name"]
            prompt = product["prompt_content"]
            image_src_val = image_src(
                product.get("product_image") or product.get("image_url")
            )

            echo(lock, f"\n  [{alias}] [{product_count + 1}/{PRODUCTS_PER_PROFILE}] {pname}")
            echo(lock, f"  Anh: {image_src_val or '(mac dinh)'}")

            try:
                create_video(page, prompt, image_src_val)
                mark_product_done(current_pid, settings["backend_key"])
                p_done += 1
                with counters_lock:
                    counters["done"] += 1
                record_product_done(profile_id, current_pid)
                echo(lock, f"  OK  [{alias}] {pname}")
                notify_product_done(settings, alias, pname)
            except VideoGenLimitReachedError:
                p_failed += 1
                with counters_lock:
                    counters["failed"] += 1
                record_product_failed(profile_id)
                echo(lock, f"  [GIOI HAN] [{alias}] {pname}: da tao toi da video trong ngay.")
                notify_profile_limit_reached(settings, alias)
                echo(lock, f"  [GIOI HAN] {alias}: Tat profile, tiep tuc voi profile khac (neu co).")
                break
            except Exception as e:
                p_failed += 1
                with counters_lock:
                    counters["failed"] += 1
                record_product_failed(profile_id)
                echo(lock, f"  LOI  [{alias}] {pname}: {e}")
                notify_product_failed(settings, alias, pname, str(e))
            finally:
                claimed.discard(current_pid)
                current_pid = None

            if product_count < PRODUCTS_PER_PROFILE - 1:
                with claim_lock:
                    try:
                        check = get_pending_products(settings["username"])
                        has_more = any(p["id"] not in claimed for p in check)
                    except Exception:
                        has_more = True
                if not has_more:
                    echo(lock, f"  [{alias}] Het san pham, ket thuc som.")
                    break
                wait_min = settings.get("wait_between_products_min", 10)
                wait_sec = wait_min * 60
                echo(lock, f"\n  [{alias}] Cho {wait_min} phut de gen video ...")
                time.sleep(wait_sec)
                echo(lock, f"  [{alias}] F5 trang ...")
                reset_page(page)

    except Exception as e:
        echo(lock, f"\n  LOI NGHIEM TRONG ({alias}): {e}")
        if current_pid is not None:
            claimed.discard(current_pid)
        with counters_lock:
            counters["failed"] += max(0, PRODUCTS_PER_PROFILE - p_done - p_failed)
    finally:
        if pw:
            try:
                pw.stop()
            except Exception:
                pass
        if proc:
            close_chrome(proc)
            echo(lock, f"\n  Da dong Chrome: {alias}")

    echo(lock, f"\n  {alias}: OK {p_done} | LOI {p_failed}")
    notify_profile_done(settings, alias, p_done, p_failed)


def cmd_run() -> None:
    """Chay tu dong tao video cho tat ca profiles."""
    settings = load_settings()

    try:
        validate_settings(settings)
    except ValueError as e:
        click.echo(f"Loi cau hinh: {e}", err=True)
        sys.exit(1)

    run_mode = settings.get("run_mode", "ordered")
    concurrent = max(1, settings.get("concurrent_profiles", 1))
    print_lock = threading.Lock()
    counters: dict = {"done": 0, "failed": 0}
    counters_lock = threading.Lock()

    if run_mode == "ordered":
        profiles = settings["profiles"]

        # Luon dong bo voi API: goi API moi lan chay de lay danh sach pending va sap xep lai
        if has_active_session():
            clear_session()
            click.echo("\n  Dong bo lai tu API (bo phien cu) ...")

        click.echo(f"\n{_BOLD}")
        click.echo("  SORA VIDEO TOOL — CHAY (THU TU)")
        click.echo(_BOLD)
        click.echo(f"  Username      : {settings['username']}")
        click.echo(f"  Profiles      : {len(profiles)}")
        click.echo(f"  Chay cung luc : {concurrent}")
        click.echo(f"  Max san pham  : {len(profiles) * PRODUCTS_PER_PROFILE}")
        click.echo(_BOLD)

        click.echo("\n  Dang lay danh sach san pham tu API (dong bo status) ...")
        try:
            products = get_pending_products(settings["username"])
        except RuntimeError as e:
            click.echo(f"Loi: {e}", err=True)
            sys.exit(1)

        if not products:
            click.echo("  Khong co san pham nao can xu ly (API). Ket thuc.")
            return

        click.echo(f"  San pham pending tu API: {len(products)}")

        # Gioi han tong san pham theo so profile
        max_sp = len(profiles) * PRODUCTS_PER_PROFILE
        if len(products) > max_sp:
            click.echo(
                f"\n  [CANH BAO] Chi xu ly duoc {max_sp}/{len(products)} san pham "
                f"(gioi han {len(profiles)} profiles)."
            )
            products = products[:max_sp]

        # Phan bo san pham cho TAT CA profiles (round-robin) theo danh sach API
        n_profiles = len(profiles)
        raw_batches: list[list] = [[] for _ in range(n_profiles)]
        for j, p in enumerate(products):
            raw_batches[j % n_profiles].append(p)

        batches_data: list[dict] = []
        for profile, batch in zip(profiles, raw_batches):
            batches_data.append({
                "profile_id": profile["id"],
                "alias": profile.get("alias", profile["id"]),
                "products": [
                    {
                        "id": p["id"],
                        "product_name": p["product_name"],
                        "prompt_content": p["prompt_content"],
                        "product_image": p.get("product_image") or p.get("image_url"),
                        "status": PENDING,
                    }
                    for p in batch
                ],
            })
        create_session(batches_data)

        with ThreadPoolExecutor(max_workers=concurrent) as pool:
            futures = [
                pool.submit(
                    _process_ordered_batch,
                    batch_info, idx, settings, print_lock, counters, counters_lock,
                )
                for idx, batch_info in enumerate(batches_data)
            ]
            for f in as_completed(futures):
                try:
                    f.result()
                except Exception as e:
                    click.echo(f"  LOI THREAD: {e}")

        mark_session_done()

    else:
        profiles = settings["profiles"]
        click.echo(f"\n{_BOLD}")
        click.echo("  SORA VIDEO TOOL — CHAY (TU DO)")
        click.echo(_BOLD)
        click.echo(f"  Username      : {settings['username']}")
        click.echo(f"  Profiles      : {len(profiles)}")
        click.echo(f"  Chay cung luc : {concurrent}")
        click.echo(_BOLD)

        claim_lock = threading.Lock()
        claimed: set = set()

        with ThreadPoolExecutor(max_workers=concurrent) as pool:
            futures = [
                pool.submit(
                    _process_greedy_profile,
                    profile, idx, settings,
                    claim_lock, claimed,
                    print_lock, counters, counters_lock,
                )
                for idx, profile in enumerate(profiles)
            ]
            for f in as_completed(futures):
                try:
                    f.result()
                except Exception as e:
                    click.echo(f"  LOI THREAD: {e}")

    click.echo(f"\n{_BOLD}")
    click.echo("  HOAN TAT")
    click.echo(_BOLD)
    click.echo(f"  Thanh cong : {counters['done']}")
    click.echo(f"  Loi        : {counters['failed']}")
    click.echo(f"  Tong       : {counters['done'] + counters['failed']}")
    click.echo(_BOLD)

    notify_all_done(settings, counters["done"], counters["failed"])


def register_run(main: click.Group) -> None:
    main.command("run")(cmd_run)
