#!/usr/bin/env python3
"""
Automated DealerKit login using the credential saved in the macOS login
keychain, the same pattern as auto_login_cazana.py, auto_login_carwow.py and
auto_login_motorway.py (Steven 2026-08-26: "do the work on dealerkit now so
it works the same as cazana").

The password is never printed, logged, written to a file, or returned from
any function here, only held in a local variable long enough to type it into
the page. It is read at run time from the macOS keychain via the `security`
command line tool, so it never lives in this repo or anywhere Claude (or
anyone reading this code) can see it.

DealerKit's own hostname is dealer specific (a different tenant subdomain
per dealership, read from dealer_config.DEALERKIT_BASE_URL, the same value
bidbrain.browser.SITES["dealerkit"] already uses), so this cannot hardcode a
single server the way the other three scripts do.

Like Carwow, and unlike Motorway, DealerKit's own login cookie is a genuine
browser session cookie (found live 2026-08-24, expires=-1), which does not
survive a fresh process launching its own Chrome, so every DealerKit read
and write already uses bidbrain.browser.open_reader_context (the saved
data/dealerkit_state.json), never the shared persistent profile. This script
does the same: logs in through a fresh, throwaway context and saves the
result to that same state file, exactly what every other DealerKit pass
already reads.

The real "logged in" signal reused here (a .vehicle-title element on the
real stock list) is the same one daily_run.py's own dealerkit_pass() already
relies on, not a new one invented for this script.

DealerKit's own login page is a plain username/password form (checked live
2026-08-26, no CAPTCHA visible), same shape as Carwow's and Motorway's; if a
CAPTCHA or some other block ever does appear, this fails loudly rather than
guessing.

One time setup, done by Steven, not by this script:
    ~/BidBrain/.venv/bin/python setup_credential.py dealerkit
  The first time this script runs, macOS may prompt for permission to read
  that keychain item. Choose "Always Allow" so future scheduled runs do not
  need anyone at the keyboard.

Usage:   python3 auto_login_dealerkit.py            headless, for real use
         python3 auto_login_dealerkit.py --headed    a visible window, to watch it work

This only ever logs in. It never reads stock or pushes anything.
"""

import sys
import subprocess
from urllib.parse import urlparse
from playwright.sync_api import sync_playwright
from bidbrain import browser, dealerkit_write, db
import dealer_config

BASE_URL = dealer_config.DEALERKIT_BASE_URL
SERVER = urlparse(BASE_URL).netloc if BASE_URL else None
LOGIN_URL = f"{BASE_URL}/login" if BASE_URL else None
# The same real stock URL, and the same real "logged in" signal
# (.vehicle-title), daily_run.dealerkit_pass() already uses.
CHECK_URL = (f"{BASE_URL}/vehicles"
             '?instance.life_cycle_status=10&instance.market=%22retail%22'
             '&instance.asset_class=%5B%22stock%22%5D') if BASE_URL else None


def _keychain_lookup(server):
    """Return (account, password) for this server from the macOS login
    keychain, or (None, None) if nothing is saved. Never prints either
    value. Raises RuntimeError (with no secret in the message) if the
    `security` tool itself is missing, which should not happen on macOS."""
    try:
        pw = subprocess.run(
            ["security", "find-internet-password", "-s", server, "-w"],
            capture_output=True, text=True,
        )
    except FileNotFoundError:
        raise RuntimeError("The macOS `security` command line tool is not available.")
    if pw.returncode != 0:
        return None, None
    password = pw.stdout.rstrip("\n")

    meta = subprocess.run(
        ["security", "find-internet-password", "-s", server],
        capture_output=True, text=True,
    )
    account = None
    for line in meta.stdout.splitlines():
        line = line.strip()
        if line.startswith('"acct"'):
            start = line.find('="')
            if start != -1:
                account = line[start + 2:-1]
            break
    return account, password


def login(headless=True):
    if not BASE_URL:
        raise RuntimeError(
            "dealer_config.DEALERKIT_BASE_URL is not set, there is nothing to log into."
        )

    account, password = _keychain_lookup(SERVER)
    if not account or not password:
        raise RuntimeError(
            f"No saved login found for {SERVER}. Run: "
            f"~/BidBrain/.venv/bin/python setup_credential.py dealerkit, then try again."
        )

    with sync_playwright() as p:
        b = p.chromium.launch(headless=headless, args=["--disable-blink-features=AutomationControlled"])
        ctx = b.new_context(viewport={"width": 1440, "height": 900})
        try:
            page = ctx.new_page()
            page.goto(LOGIN_URL, wait_until="domcontentloaded", timeout=30000)
            try:
                page.wait_for_selector('input[name="username"]', timeout=15000)
            except Exception:
                raise RuntimeError(
                    "DealerKit's login page did not show the expected username field. "
                    "The page may have changed, check it by eye (python3 login.py dealerkit)."
                )
            page.fill('input[name="username"]', account)
            page.fill('input[name="password"]', password)
            password = None  # cleared as soon as it is no longer needed
            try:
                page.click('button[type="submit"]', timeout=5000)
            except Exception:
                raise RuntimeError("Could not find the DealerKit login submit button.")

            # No fixed "logged in" selector to wait on the login page itself;
            # instead land on the real stock list and look for a real
            # vehicle row, same signal daily_run.dealerkit_pass() already
            # uses. A wrong password, a captcha, or a changed page all land
            # here, never guessed which, just fail loudly.
            try:
                page.goto(CHECK_URL, wait_until="domcontentloaded", timeout=30000)
                page.wait_for_selector(".vehicle-title", timeout=15000)
            except Exception:
                err = page.query_selector('[class*="error" i], [role="alert"]')
                detail = err.inner_text().strip() if err else (
                    "the real stock list never showed up, a wrong password or "
                    "a captcha/bot check may have blocked it")
                raise RuntimeError(f"DealerKit login did not succeed: {detail}")

            # The API session, not only the page (2026-09-07): the saved
            # state is only worth keeping once a real API read answers
            # with data, DealerKit finishes signing the API side in a
            # moment after the page shows, and a state saved before that
            # passed every page check while every API call failed.
            api_ok = False
            for _ in range(10):
                if dealerkit_write._session_probe(lambda url: ctx.request.get(url, timeout=15000).json()):
                    api_ok = True
                    break
                page.wait_for_timeout(1500)
            if not api_ok:
                raise RuntimeError("DealerKit login showed the stock list but its API still served the sign in page after 15 seconds. Try again.")
            ctx.storage_state(path=browser.state_path("dealerkit"))
            # A successful login lands on the real stock list (a genuine
            # data read, not just a login form disappearing), so this is
            # real, current evidence the site is reachable, worth clearing
            # any earlier real-read failure that would otherwise sit there
            # stale until the next successful read happens to come along
            # (Steven 2026-08-26, caught live: a red flag with "Logged in
            # within the last day" right next to it, confusing).
            db.record_site_health("dealerkit", True, "")
            print("Logged in to DealerKit. Session saved.")
        finally:
            ctx.close()
            b.close()


if __name__ == "__main__":
    headless = "--headed" not in sys.argv
    try:
        login(headless=headless)
    except Exception as e:
        print(f"FAILED: {e}")
        sys.exit(1)
