#!/usr/bin/env python3
"""
Automated Dealer Auction login using the credential saved in the macOS login
keychain, the same pattern as auto_login_cazana.py, auto_login_carwow.py,
auto_login_motorway.py and auto_login_dealerkit.py (Mark 2026-08-27: "update
DA, A4C, LE to use the auto login that DK, Cazana & MW & Carwow use").

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.

TWO real, structural things make Dealer Auction different from the other
four automated logins, both found live 2026-08-27, both worth remembering:

1. Dealer Auction's login page sits behind Cloudflare bot management.
   Playwright's own default headless fingerprint (default user agent, no
   extra flags) was outright blocked, "Sorry, you have been blocked", on the
   very first live attempt. A real desktop Chrome user agent plus the same
   --disable-blink-features=AutomationControlled flag already used by every
   other script here was enough to pass, proven live headless. If Cloudflare
   ever tightens this further and blocks again, this fails loudly with the
   real block page text rather than guessing at a workaround.

2. bidbrain.browser.py's own SITES entry says plainly: "Login has an SMS 2FA
   step sometimes, so the first login (and any re challenge) needs Mark
   actually present". A one time code sent by SMS cannot be supplied by an
   unattended script, there is no way around that. This script does not try:
   it watches for a real OTP/code input appearing on the page and, if one
   does, fails loudly with a clear message telling Mark to finish this one
   manually (python3 login.py dealerauction manual), rather than hang
   forever waiting for a code nobody can give it. Most days there should be
   no challenge at all (Cloudflare's own block above is the one seen so far
   in testing), this is a safety net for the days there is.

Like Carwow, DealerKit, Auction4Cars and LE Capital, and unlike Motorway,
Dealer Auction's own session does not survive a fresh process launching its
own Chrome, so this logs in through a fresh throwaway context and saves the
result to data/dealerauction_state.json, the same file every other Dealer
Auction read (bidbrain.readers.dealerauction) already uses via
bidbrain.browser.open_reader_context.

The real "logged in" signal reused here (a real advert link on the stock
list) is the same one bidbrain.readers.dealerauction.read_live() already
relies on, not a new one invented for this script.

One time setup, done by Mark, not by this script:
    python3 setup_credential.py dealerauction
  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_dealerauction.py            headless, for real use
         python3 auto_login_dealerauction.py --headed    a visible window, to watch it work

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

import sys
import subprocess
from playwright.sync_api import sync_playwright
from bidbrain import browser, db

SERVER = "portal.autotrader.co.uk"
LOGIN_URL = "https://portal.autotrader.co.uk/dealerauction-login/"
STOCK_URL = (
    "https://www.dealerauction.co.uk/sourcing/#/search"
    "?network=trade_independent&network=trade_franchise&network=trade_fleet"
    "&network=retail_ready&network=c2b&network=c2b_generic"
    "&network=digital_wholesale&network=hertz&network=manheim_digital_first"
    "&salesMethod=bid"
)
# A real desktop Chrome UA, needed to get past Cloudflare's own bot check on
# the login page itself, see the module docstring above.
REAL_UA = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
           "(KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")


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):
    account, password = _keychain_lookup(SERVER)
    if not account or not password:
        raise RuntimeError(
            f"No saved login found for {SERVER}. Run: "
            f"python3 setup_credential.py dealerauction, 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}, user_agent=REAL_UA)
        try:
            page = ctx.new_page()
            page.goto(LOGIN_URL, wait_until="domcontentloaded", timeout=30000)
            page.wait_for_timeout(1500)

            body_text = ""
            try:
                body_text = page.inner_text("body")
            except Exception:
                pass
            if "blocked" in body_text.lower() and "cloudflare" in body_text.lower():
                raise RuntimeError(
                    "Dealer Auction's login page blocked this automated request "
                    "(Cloudflare). Try again later, or log in by hand: "
                    "python3 login.py dealerauction manual"
                )

            try:
                page.wait_for_selector('input[type="email"]', timeout=15000)
            except Exception:
                raise RuntimeError(
                    "Dealer Auction's login page did not show the expected email field. "
                    "The page may have changed, check it by eye (python3 login.py dealerauction manual)."
                )
            page.fill('input[type="email"]', account)
            page.fill('input[type="password"]', password)
            password = None  # cleared as soon as it is no longer needed
            try:
                page.click('button[data-testid="login-button"]', timeout=5000)
            except Exception:
                raise RuntimeError("Could not find the Dealer Auction login submit button.")

            # Watch for a one time code field, which this script cannot ever
            # complete on its own, see the module docstring above.
            page.wait_for_timeout(2500)
            try:
                otp = page.query_selector(
                    'input[autocomplete="one-time-code"], input[name*="otp" i], '
                    'input[name*="code" i], input[placeholder*="code" i]'
                )
            except Exception:
                otp = None
            if otp:
                raise RuntimeError(
                    "Dealer Auction is asking for a one time SMS code. This "
                    "cannot be completed automatically, finish it by hand: "
                    "python3 login.py dealerauction manual"
                )

            # Same real signal bidbrain.readers.dealerauction.read_live()
            # already relies on: a real advert link on the stock list.
            try:
                page.goto(STOCK_URL, wait_until="networkidle", timeout=30000)
                page.wait_for_selector('a[analyticslabel="view-advert"]', timeout=20000)
            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, a "
                    "changed page, or a login challenge may be the cause")
                raise RuntimeError(f"Dealer Auction login did not succeed: {detail}")

            ctx.storage_state(path=browser.state_path("dealerauction"))
            db.record_site_health("dealerauction", True, "")
            print("Logged in to Dealer Auction. 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)
