"""
DealerKit reader.

Right Drive's DMS (2026-08-19), replacing Clickdealer. Reads the in stock
list for the daily stock count used by the gap / in stock / plenty flag on
the cockpit. bidbrain.readers.clickdealer.model_key is the shared matching
key (first token of make plus first token of model), reused unchanged so a
DealerKit "NISSAN QASHQAI" and a Motorway "Nissan Qashqai Tekna dCi" still
meet on the same key.

Nothing here bids or acts. It reads the screen only.
"""

import re
from .. import browser
from . import clickdealer

TITLE_RE = re.compile(r'class="vehicle-title">([^<]+)</span>')
VEHICLE_ID_RE = re.compile(r'/vehicles/(\d+)/overview')
NEXT_PAGE_SEL = 'i.material-icons:has-text("keyboard_arrow_right")'

# The in stock view: retail market, stock asset class, life cycle status 10,
# the same filter as Mark's own Vehicles saved view (captured 2026-08-19).
# Counts every location on the account (Mark's choice, checked live: the
# account spans RightDrive Car Finance, STC Automotive and Kirks Works).
STOCK_URL = (f"{browser.SITES['dealerkit']['base_url']}/vehicles"
            '?instance.life_cycle_status=10&instance.market=%22retail%22'
            '&instance.asset_class=%5B%22stock%22%5D')


def parse_stock(html):
    """Parse one page of the Vehicles (in stock) list into {model_key: count}
    plus the raw (make, model) pairs, and the set of vehicle ids on the page
    (used by read_in_stock to know when pagination has run out, the page's
    Next control is a plain span with no reliable disabled attribute to read,
    so this is the robust signal instead). Returns (counts, pairs, ids).
    Every vehicle-title is "MAKE MODEL...", the first word is always the
    make (model_key only ever needs each side's first token anyway, so a
    compound make like Mercedes-Benz still keys correctly without special
    casing it here)."""
    counts = {}
    pairs = []
    ids = set(VEHICLE_ID_RE.findall(html))
    for title in TITLE_RE.findall(html):
        toks = title.strip().split(None, 1)
        if len(toks) < 2:
            continue
        make, model = toks[0], toks[1]
        key = clickdealer.model_key(make, model)
        if not key:
            continue
        counts[key] = counts.get(key, 0) + 1
        pairs.append((make, model))
    return counts, pairs, ids


def stock_record_row(vehicle_id, data):
    """One in stock car as DealerKit's own stocklist record describes it
    (2026-09-10, Steven: "Dealerkit is always the source of truth, the
    paid price should never be not known"): the plate, make and model,
    DealerKit's purchase cost and retail price, and its stage number.
    Anything the record does not carry is None, never guessed."""
    d = data or {}
    v = d.get("vehicle") or {}
    reg_obj = v.get("registration")
    reg = (reg_obj.get("vrm") if isinstance(reg_obj, dict) else reg_obj) or d.get("vrm") or d.get("registration") or ""
    reg = "".join(str(reg).upper().split())
    def _num(x):
        try:
            return None if x is None or x == "" else int(round(float(x)))
        except (TypeError, ValueError):
            return None
    def _text(x):
        # DealerKit's record carries the model as its own object (found
        # live 2026-09-10: "type 'dict' is not supported" on the save).
        if isinstance(x, dict):
            x = x.get("name") or x.get("label") or x.get("value") or x.get("title") or ""
        return "" if x is None else str(x)
    from .. import dealerkit_write
    pc = d.get("purchase_cost")
    return {
        "vehicle_id": str(vehicle_id),
        "reg": reg,
        "make": _text(v.get("make") or v.get("manufacturer") or d.get("make")),
        "model": _text(v.get("model") or d.get("model")),
        # DealerKit's purchase cost is an expense object, the Chassis line
        # is the price paid (dealerkit_write.purchase_cost_figure).
        "purchase_cost": dealerkit_write.purchase_cost_figure(pc) if isinstance(pc, dict) else _num(pc),
        "retail_price": _num(d.get("retail_price")),
        "life_cycle_status": d.get("life_cycle_status"),
    }


def read_stock_records(page, ids):
    """Read DealerKit's own record for every in stock vehicle id (the same
    /api/stocklist read the stage check makes), for the purchase cost of
    cars BidBrain never bought. A record that will not read is skipped
    with a line, never the whole pass. Returns the rows read."""
    from .. import dealerkit_write
    rows = []
    shown = False
    for vid in sorted(ids):
        try:
            data = dealerkit_write.read_record(page, vid)
            row = stock_record_row(vid, data)
            if not shown and isinstance(data.get("purchase_cost"), dict):
                # Say what the object looks like, once, so a figure that
                # still reads as None can be pinned down from the log.
                pc = data["purchase_cost"]
                items = ((pc.get("expense") or {}).get("items")) or []
                print(f"  DealerKit purchase cost shape: keys {sorted(pc.keys())}, "
                      f"first item {sorted(items[0].keys()) if items and isinstance(items[0], dict) else 'none'}, "
                      f"read as {row['purchase_cost']}")
                shown = True
            rows.append(row)
        except dealerkit_write.DealerkitSessionExpired:
            raise
        except Exception as e:
            print(f"  DealerKit record {vid} not read: {e}")
    return rows


def read_in_stock(playwright, headless=True, with_records=False):
    """Open DealerKit's in stock list (retail market, stock asset class, the
    same filter as Mark's own Vehicles saved view) and return (counts,
    pairs) across every page. Fails loudly if the list does not render,
    never guesses."""
    ctx = browser.open_reader_context(playwright, "dealerkit", headless=headless)
    try:
        page = ctx.pages[0] if ctx.pages else ctx.new_page()
        page.goto(STOCK_URL, wait_until="domcontentloaded", timeout=60000)
        try:
            page.wait_for_selector(".vehicle-title", timeout=30000)
        except Exception:
            raise RuntimeError(
                "DealerKit stock list did not render any vehicles "
                f"(no vehicle-title at {page.url}). The page may have "
                "changed or the login may have expired. Run: python3 login.py dealerkit manual"
            )
        page.wait_for_timeout(1000)
        counts, pairs, ids = read_all_pages_with_ids(page)
        if not counts:
            raise RuntimeError(
                "DealerKit stock list rendered but no vehicle rows were read. "
                "The layout may have changed, not guessing."
            )
        if with_records:
            return counts, pairs, read_stock_records(page, ids)
        return counts, pairs
    finally:
        ctx.close()


def read_all_pages(page):
    """(counts, pairs) across every page, see read_all_pages_with_ids."""
    counts, pairs, _ids = read_all_pages_with_ids(page)
    return counts, pairs


def read_all_pages_with_ids(page):
    """Accumulate {model_key: count} and (make, model) pairs across every
    page of the in stock list, from an already open, already logged in,
    already navigated page (the caller owns the page and its context, the
    same shape as Carwow's and Auction4Cars' read_all_pages, so an on demand
    refresh pass that already has a headed, logged in page open can reuse
    this rather than only ever reading page 1)."""
    counts = {}
    pairs = []
    seen_ids = set()
    for _ in range(50):  # a generous safety cap, the real list is one or two pages
        html = page.content()
        page_counts, page_pairs, ids = parse_stock(html)
        new_ids = ids - seen_ids
        if not new_ids and seen_ids:
            break  # this page repeats the last one, pagination has run out
        seen_ids |= ids
        for k, n in page_counts.items():
            counts[k] = counts.get(k, 0) + n
        pairs.extend(page_pairs)

        nxt = page.query_selector(NEXT_PAGE_SEL)
        if not nxt:
            break
        try:
            nxt.click(timeout=3000)
        except Exception:
            break  # disabled or gone, no next page
        page.wait_for_timeout(1500)
    return counts, pairs, seen_ids
