"""
5th API — get video list from the exchange wizard.

Classic Basketball UI uses #events-container + ko-choice-label.
Football (and newer Hudl) uses the multi-step smooth-modal:
  .smooth-modal .multi-step-modal section.step-2  → "Choose a Game to Exchange"

Do NOT call close_hudl_popups() here — that dismisses the wizard modal
and leaves the frontend with an empty video list.
"""
import asyncio
import re

from playwright.async_api import Locator, Page

from app.automation.helpers import human_delay, find_and_click_next
from app.automation.selectors import VIDEO_LIST_ITEMS

# Prefer Football multi-step modal when present; classic Basketball list otherwise.
_MULTI_STEP_SELECTORS = (
    ".multi-step-modal section.step-2 li",
    ".multi-step-modal-inner.step-2-active section.step-2 li",
    ".smooth-modal section.step-2 li",
    ".multi-step-modal section.step-2 .ko-choice-list > li",
    ".multi-step-modal section.step-2 [class*='game']",
    ".multi-step-modal section.step-2 [class*='event']",
    ".multi-step-modal section.step-2 [role='button']",
)
_CLASSIC_SELECTORS = (
    VIDEO_LIST_ITEMS,
    "#events-container ul > li",
    "#events-container li",
    "section#events-container li",
    "ul.ko-choice-list > li",
)

_LABEL_SELECTORS = (
    "span.ko-choice-label",
    ".ko-choice-label",
    "label.ko-choice-label",
    "[class*='choice-label']",
    ".ex-video-title",
    ".video-title",
    "[class*='title']",
    "label",
)


async def _multi_step_modal_open(page: Page) -> bool:
    try:
        return await page.locator(
            ".multi-step-modal, .smooth-modal .multi-step-modal, "
            ".multi-step-modal-inner.step-2-active, text=Choose a Game to Exchange"
        ).count() > 0
    except Exception:
        return False


async def _first_matching_list(page: Page) -> Locator | None:
    # When Football (or any) multi-step wizard is open, NEVER scrape classic
    # #events-container leftovers — that breaks Basketball-style empty reads.
    selectors = (
        (*_MULTI_STEP_SELECTORS, *_CLASSIC_SELECTORS)
        if await _multi_step_modal_open(page)
        else (*_CLASSIC_SELECTORS, *_MULTI_STEP_SELECTORS)
    )
    for selector in selectors:
        loc = page.locator(selector)
        try:
            count = await loc.count()
            if count <= 0:
                continue
            # Prefer a list that has at least one visible row (step-2 vs hidden step-1).
            visible_count = 0
            check_n = min(count, 12)
            for i in range(check_n):
                try:
                    if await loc.nth(i).is_visible():
                        visible_count += 1
                        break
                except Exception:
                    continue
            if visible_count > 0:
                print(f"get_video_list: using selector '{selector}' ({count} items, visible)")
                return loc
            print(f"get_video_list: using selector '{selector}' ({count} attached)")
            return loc
        except Exception:
            continue
    return None


async def _parse_video_li(item: Locator, index: int) -> dict | None:
    class_attr = (await item.get_attribute("class") or "").strip()
    if "bad-status" in class_attr:
        return None

    title = ""
    for sel in _LABEL_SELECTORS:
        label_el = item.locator(sel).first
        try:
            if await label_el.count() == 0:
                continue
            raw = await label_el.inner_text()
            title = (raw or "").strip()
            if title:
                break
        except Exception:
            continue

    if not title:
        try:
            raw = await item.inner_text()
            for line in (raw or "").splitlines():
                cleaned = line.strip()
                if cleaned:
                    title = cleaned
                    break
        except Exception:
            title = ""

    if not title:
        return None
    if re.fullmatch(
        r"(select|next|back|cancel|choose a game to exchange|exchanging with.*|"
        r"\d{4}-\d{4} season)",
        title,
        re.I,
    ):
        return None

    return {"index": index, "title": title}


async def _scrape_videos_via_js(page: Page) -> list[dict]:
    """Scrape classic events-container OR Football multi-step modal step-2."""
    rows = await page.evaluate(
        """() => {
          const items = [];
          const seen = new Set();

          const pushEl = (el, index) => {
            if (!el || !(el instanceof Element)) return;
            const cls = (el.className || '').toString();
            if (cls.includes('bad-status')) return;
            const style = window.getComputedStyle(el);
            if (style && (style.display === 'none' || style.visibility === 'hidden')) return;

            const label =
              el.querySelector(
                'span.ko-choice-label, .ko-choice-label, [class*="choice-label"], ' +
                '.ex-video-title, .video-title, [class*="title"], label'
              ) || el;
            let title = (label.innerText || '')
              .trim()
              .split('\\n')
              .map(s => s.trim())
              .find(Boolean) || '';
            title = title.replace(/\\s+/g, ' ').trim();
            if (!title) return;
            if (/^(select|next|back|cancel)$/i.test(title)) return;
            if (/choose a game to exchange/i.test(title)) return;
            if (/^exchanging with/i.test(title)) return;
            if (/^\\d{4}-\\d{4}\\s+season$/i.test(title)) return;
            const key = title.toLowerCase();
            if (seen.has(key)) return;
            seen.add(key);
            items.push({ index: items.length, title });
          };

          const roots = [
            document.querySelector('.multi-step-modal section.step-2'),
            document.querySelector('.multi-step-modal-inner.step-2-active section.step-2'),
            document.querySelector('.smooth-modal section.step-2'),
            document.querySelector('#events-container'),
            document.querySelector('section#events-container'),
          ].filter(Boolean);

          for (const root of roots) {
            const style = window.getComputedStyle(root);
            if (style && style.display === 'none') continue;

            // Prefer list rows, then clickable game rows.
            const candidates = root.querySelectorAll(
              'li, [role="option"], [role="button"], button, a, ' +
              '[class*="game"], [class*="event"], [class*="playlist"], [class*="video"]'
            );
            if (candidates.length) {
              candidates.forEach((el) => pushEl(el, items.length));
              if (items.length) return items;
            }
          }

          // Last resort: any visible ko-choice rows.
          document.querySelectorAll('ul.ko-choice-list > li, .ko-choice-list > li')
            .forEach((el) => pushEl(el, items.length));
          return items;
        }"""
    )
    return rows or []


async def _wait_for_video_step(page: Page) -> None:
    """Wait until classic events list OR Football step-2 modal is present."""
    try:
        await page.locator(
            "#events-container, "
            "section#events-container, "
            ".multi-step-modal section.step-2, "
            ".smooth-modal section.step-2, "
            "text=Choose a Game to Exchange"
        ).first.wait_for(state="attached", timeout=12000)
    except Exception:
        pass


async def get_video_list(page: Page) -> dict:
    """
    GET /exchange/videos
    Reads selectable videos/games from the exchange wizard.
    """
    # Intentionally NOT closing popups — Football videos live inside smooth-modal.

    videos: list[dict] = []
    skipped = 0
    used_selector = None
    last_total = 0

    for attempt in range(8):
        await human_delay(0.6, 1.2)
        await _wait_for_video_step(page)

        js_rows = await _scrape_videos_via_js(page)
        if js_rows:
            videos = js_rows
            print(
                f"get_video_list: JS scrape found {len(videos)} videos "
                f"(attempt {attempt + 1}, url={page.url})"
            )
            break

        items_locator = await _first_matching_list(page)
        if items_locator is None:
            print(
                f"get_video_list attempt {attempt + 1}: no list yet (url={page.url})"
            )
            await asyncio.sleep(1.0)
            continue

        used_selector = True
        total = await items_locator.count()
        last_total = total
        videos = []
        skipped = 0
        for i in range(total):
            row = await _parse_video_li(items_locator.nth(i), i)
            if row:
                videos.append(row)
            else:
                skipped += 1

        if videos:
            break

        print(
            f"get_video_list attempt {attempt + 1}: {total} nodes found, "
            f"0 parseable (url={page.url})"
        )
        await asyncio.sleep(1.0)

    message = (
        f"Found {len(videos)} videos."
        if videos
        else "No video items found on page."
    )
    ui_mode = await _detect_ui_mode(page)
    # If we scraped from the multi-step game modal, force multi_step even if
    # classic detectors missed it.
    if ui_mode == "classic":
        try:
            if await page.locator(
                "text=Choose a Game to Exchange, .multi-step-modal section.step-2"
            ).count() > 0:
                ui_mode = "multi_step"
        except Exception:
            pass

    step_title = (
        "Choose a Game to Exchange"
        if ui_mode == "multi_step"
        else "Select the video(s) you want to send."
    )
    print(
        f"get_video_list done: videos={len(videos)} skipped={skipped} "
        f"raw_li={last_total} selector={used_selector} ui_mode={ui_mode} url={page.url}"
    )

    return {
        "status": "ok",
        "url": page.url,
        "videos": videos,
        "videos_count": len(videos),
        "skipped_bad_status": skipped,
        "ui_mode": ui_mode,
        "step_title": step_title,
        "step_subtitle": "Exchanging with" if ui_mode == "multi_step" else None,
        "message": (
            f"Found {len(videos)} games."
            if ui_mode == "multi_step" and videos
            else message
        ),
    }


async def _detect_ui_mode(page: Page) -> str:
    if await _multi_step_modal_open(page):
        return "multi_step"
    return "classic"


async def _read_playlist_step(page: Page) -> dict:
    """Read Hudl 'Choose Playlist(s)' step (including empty state)."""
    data = await page.evaluate(
        """() => {
          const modal =
            document.querySelector('.smooth-modal, .multi-step-modal') || document.body;
          const text = (modal.innerText || '');
          const isPlaylistStep =
            /choose playlist/i.test(text) ||
            /no playlists/i.test(text) ||
            !!document.querySelector('.multi-step-modal-inner.step-3-active, section.step-3');

          if (!isPlaylistStep) {
            return { on_playlist_step: false, playlists: [], playlists_count: 0 };
          }

          const empty = /no playlists/i.test(text);
          const emptyTitleMatch = text.match(/No Playlists/i);
          const emptyMessageMatch = text.match(
            /There aren't any playlists here to exchange[^.]*\\.?/i
          );

          const playlists = [];
          const seen = new Set();
          const root =
            document.querySelector('.multi-step-modal section.step-3') ||
            document.querySelector('.multi-step-modal-inner.step-3-active') ||
            document.querySelector('.smooth-modal') ||
            modal;

          root.querySelectorAll('li, [role="option"], [role="checkbox"], label, [class*="playlist"]').forEach((el) => {
            const style = window.getComputedStyle(el);
            if (style && (style.display === 'none' || style.visibility === 'hidden')) return;
            let title = (el.innerText || '').trim().split('\\n').map(s => s.trim()).find(Boolean) || '';
            title = title.replace(/\\s+/g, ' ').trim();
            if (!title || title.length > 120) return;
            if (/^(cancel|next|add more|0 playlists|choose playlist)/i.test(title)) return;
            if (/no playlists|there aren't any playlists/i.test(title)) return;
            const key = title.toLowerCase();
            if (seen.has(key)) return;
            seen.add(key);
            playlists.push({ index: playlists.length, title });
          });

          const countMatch = text.match(/(\\d+)\\s+Playlists?/i);
          const headerCount = countMatch ? parseInt(countMatch[1], 10) : playlists.length;

          return {
            on_playlist_step: true,
            playlists,
            playlists_count: playlists.length,
            header_count: headerCount,
            empty: empty || playlists.length === 0,
            empty_title: emptyTitleMatch ? 'No Playlists' : (playlists.length ? null : 'No Playlists'),
            empty_message: emptyMessageMatch
              ? emptyMessageMatch[0].trim()
              : (playlists.length
                  ? null
                  : "There aren't any playlists here to exchange. Tap the back arrow to choose a different game."),
            can_proceed: playlists.length > 0,
            step_title: 'Choose Playlist(s)',
          };
        }"""
    )
    return data or {"on_playlist_step": False, "playlists": [], "playlists_count": 0}


async def _click_game_rows(page: Page, video_indices: list[int]) -> list[int]:
    selected: list[int] = []
    items_locator = await _first_matching_list(page)
    js_rows = await _scrape_videos_via_js(page)

    if items_locator is not None:
        total = await items_locator.count()
        for idx in video_indices:
            if idx < 0 or idx >= total:
                continue
            item = items_locator.nth(idx)
            class_attr = (await item.get_attribute("class") or "").strip()
            if "bad-status" in class_attr:
                continue
            await item.scroll_into_view_if_needed()
            await human_delay(0.4, 0.8)
            await item.click()
            selected.append(idx)
            # Football multi-step: clicking a game opens playlist step — stop after first.
            await human_delay(0.8, 1.2)
            playlist = await _read_playlist_step(page)
            if playlist.get("on_playlist_step"):
                break
        return selected

    for idx in video_indices:
        if idx < 0 or idx >= len(js_rows):
            continue
        title = js_rows[idx]["title"]
        clicked = await page.evaluate(
            """(title) => {
              const root =
                document.querySelector('.multi-step-modal section.step-2') ||
                document.querySelector('#events-container') ||
                document.body;
              const nodes = Array.from(root.querySelectorAll(
                'li, [role="button"], button, a, [class*="game"], [class*="event"]'
              ));
              const el = nodes.find(n => (n.innerText || '').includes(title));
              if (!el) return false;
              el.click();
              return true;
            }""",
            title,
        )
        if clicked:
            selected.append(idx)
            await human_delay(0.8, 1.2)
            playlist = await _read_playlist_step(page)
            if playlist.get("on_playlist_step"):
                break
    return selected


async def select_videos(page: Page, video_indices: list[int]) -> dict:
    """
    POST /exchange/videos/select

    Classic Basketball: select rows + click Next → stats/notes.
    Football multi-step: click game → lands on Choose Playlist(s)
    (may be empty — mirror Hudl message, do NOT force Next).
    """
    await human_delay(0.6, 1.0)
    await _wait_for_video_step(page)
    ui_mode = await _detect_ui_mode(page)

    selected = await _click_game_rows(page, video_indices)
    if not selected:
        return {
            "status": "error",
            "action": "video_select",
            "selected_indices": [],
            "ui_mode": ui_mode,
            "url": page.url,
            "message": "No video/game list found on page to select from.",
        }

    await human_delay(0.8, 1.4)
    playlist = await _read_playlist_step(page)

    # Football / multi-step: only treat as playlist step when Hudl actually shows it.
    if playlist.get("on_playlist_step"):
        empty = bool(playlist.get("empty") or not playlist.get("playlists"))
        return {
            "status": "ok",
            "action": "video_select",
            "selected_indices": selected,
            "ui_mode": "multi_step",
            "wizard_step": "playlists",
            "playlists": playlist.get("playlists") or [],
            "playlists_count": int(playlist.get("playlists_count") or 0),
            "empty": empty,
            "empty_title": playlist.get("empty_title") or "No Playlists",
            "empty_message": playlist.get("empty_message")
            or "There aren't any playlists here to exchange. Tap the back arrow to choose a different game.",
            "can_proceed": bool(playlist.get("can_proceed")),
            "step_title": playlist.get("step_title") or "Choose Playlist(s)",
            "url": page.url,
            "message": (
                "No Playlists"
                if empty
                else f"Found {playlist.get('playlists_count') or 0} playlists."
            ),
        }

    # Multi-step game click sometimes needs a short wait before playlist paints.
    if ui_mode == "multi_step" or await _multi_step_modal_open(page):
        for _ in range(8):
            await asyncio.sleep(0.5)
            playlist = await _read_playlist_step(page)
            if playlist.get("on_playlist_step"):
                empty = bool(playlist.get("empty") or not playlist.get("playlists"))
                return {
                    "status": "ok",
                    "action": "video_select",
                    "selected_indices": selected,
                    "ui_mode": "multi_step",
                    "wizard_step": "playlists",
                    "playlists": playlist.get("playlists") or [],
                    "playlists_count": int(playlist.get("playlists_count") or 0),
                    "empty": empty,
                    "empty_title": playlist.get("empty_title") or "No Playlists",
                    "empty_message": playlist.get("empty_message")
                    or "There aren't any playlists here to exchange. Tap the back arrow to choose a different game.",
                    "can_proceed": bool(playlist.get("can_proceed")),
                    "step_title": playlist.get("step_title") or "Choose Playlist(s)",
                    "url": page.url,
                    "message": (
                        "No Playlists"
                        if empty
                        else f"Found {playlist.get('playlists_count') or 0} playlists."
                    ),
                }

    # Classic Basketball flow — click Next to advance to stats/notes.
    found_next_button = await find_and_click_next(page)
    await human_delay(1.2, 2.0)
    return {
        "status": "ok",
        "action": "video_select",
        "selected_indices": selected,
        "ui_mode": "classic",
        "wizard_step": "stats_or_notes",
        "url": page.url,
        "next_button_used": found_next_button,
        "can_proceed": True,
        "message": f"Selected {len(selected)} videos and clicked Next.",
    }


async def select_playlists(page: Page, playlist_indices: list[int] | None = None) -> dict:
    """
    POST /exchange/playlists/select
    Select playlist rows on Choose Playlist(s), then click Next when enabled.
    """
    await human_delay(0.5, 0.9)
    playlist = await _read_playlist_step(page)
    if not playlist.get("on_playlist_step"):
        return {
            "status": "error",
            "action": "playlist_select",
            "url": page.url,
            "message": "Not on the Choose Playlist(s) step.",
        }

    rows = playlist.get("playlists") or []
    if not rows:
        return {
            "status": "ok",
            "action": "playlist_select",
            "wizard_step": "playlists",
            "playlists": [],
            "playlists_count": 0,
            "empty": True,
            "empty_title": "No Playlists",
            "empty_message": playlist.get("empty_message")
            or "There aren't any playlists here to exchange. Tap the back arrow to choose a different game.",
            "can_proceed": False,
            "url": page.url,
            "message": "No Playlists",
        }

    indices = playlist_indices if playlist_indices is not None else list(range(len(rows)))
    selected = []
    for idx in indices:
        if idx < 0 or idx >= len(rows):
            continue
        title = rows[idx]["title"]
        clicked = await page.evaluate(
            """(title) => {
              const root =
                document.querySelector('.multi-step-modal section.step-3') ||
                document.querySelector('.smooth-modal') ||
                document.body;
              const nodes = Array.from(root.querySelectorAll(
                'li, [role="option"], [role="checkbox"], label, [class*="playlist"]'
              ));
              const el = nodes.find(n => (n.innerText || '').includes(title));
              if (!el) return false;
              el.click();
              return true;
            }""",
            title,
        )
        if clicked:
            selected.append(idx)
            await human_delay(0.3, 0.6)

    await human_delay(0.6, 1.0)
    try:
        next_sel = await find_and_click_next(page)
        await human_delay(1.0, 1.6)
        return {
            "status": "ok",
            "action": "playlist_select",
            "selected_indices": selected,
            "wizard_step": "stats_or_notes",
            "can_proceed": True,
            "next_button_used": next_sel,
            "url": page.url,
            "message": f"Selected {len(selected)} playlists and clicked Next.",
        }
    except Exception as exc:
        return {
            "status": "error",
            "action": "playlist_select",
            "selected_indices": selected,
            "can_proceed": False,
            "url": page.url,
            "message": str(exc),
        }
