"""
Google Flights search via camoufox + residential proxy.

Usage:
    python3 search.py --origin LGW --destination PMI --date 2026-04-24
    python3 search.py --origin LGW --destination TRD --date 2026-05-25 \
        --trip-type return --return-date 2026-05-28 \
        --adults 2 --children 1 --infants-on-lap 1

Prints structured JSON results to stdout.
Screenshots saved to /tmp/google-flights-*.png for debugging.
"""

import asyncio
import argparse
import json
import sys
from datetime import datetime
from camoufox.async_api import AsyncCamoufox

PROXY = {"server": "socks5://172.20.0.1:1055"}


async def dismiss_consent(page):
    try:
        btn = page.locator(
            "button:has-text('Accept all'), "
            "button:has-text('Reject all'), "
            "button:has-text('I agree'), "
            "button:has-text('Accept')"
        )
        if await btn.count() > 0:
            await btn.first.click()
            await asyncio.sleep(1.5)
    except Exception:
        pass


async def set_trip_type(page, trip_type):
    """
    Set one-way or round trip.

    Strategy: find the trip-type button by its aria-label or visible text,
    click it to open ITS listbox, then scope the option click to only
    that listbox — avoiding any clash with the passengers dropdown.
    """
    try:
        # The trip-type selector button typically has aria-label containing
        # "Round trip" or "Change ticket type" or similar.
        # It lives inside the top-left toolbar area.
        trip_btn = page.locator(
            '[data-value="1"], [data-value="2"], '
            'button[aria-label*="Round trip"], '
            'button[aria-label*="One way"], '
            'button[aria-label*="ticket type"]'
        ).first

        if await trip_btn.count() == 0:
            # Fallback: first button-like element in the search form header
            # that contains trip-type text
            trip_btn = page.locator(
                'div[jsname="K9BBDb"] button, '
                'span:has-text("Round trip"), '
                'span:has-text("One way")'
            ).first

        if await trip_btn.count() == 0:
            print("[warn] Trip type button not found, skipping", file=sys.stderr)
            return

        await trip_btn.click()
        await asyncio.sleep(0.8)

        # After clicking, a listbox/menu opens. Scope subsequent option click
        # to the currently-open listbox to avoid collision with other dropdowns.
        listbox = page.locator('[role="listbox"]:visible, [role="menu"]:visible').first
        if await listbox.count() == 0:
            print("[warn] Trip type listbox not visible, skipping", file=sys.stderr)
            return

        # "One way" is typically data-value="2", "Round trip" is data-value="1"
        if trip_type == "one-way":
            opt = listbox.locator('[data-value="2"], li:has-text("One way")').first
        else:
            opt = listbox.locator('[data-value="1"], li:has-text("Round trip")').first

        if await opt.count() > 0:
            await opt.click()
            await asyncio.sleep(0.5)
        else:
            print(f"[warn] Trip type option for '{trip_type}' not found", file=sys.stderr)

    except Exception as e:
        print(f"[warn] set_trip_type failed: {e}", file=sys.stderr)


async def set_passengers(page, adults=1, children=0,
                         infants_in_seat=0, infants_on_lap=0):
    """
    Open the travellers dialog and set passenger counts.

    Google Flights aria-labels:
      "Increase number of Adults"   / "Decrease number of Adults"
      "Increase number of Children" / "Decrease number of Children (ages 2-11)"
      "Increase number of Infants in seat" / ...
      "Increase number of Infants on lap"  / ...
    """
    # Skip if all defaults (1 adult, nothing else)
    if adults == 1 and children == 0 and infants_in_seat == 0 and infants_on_lap == 0:
        return

    try:
        # Click the travellers / passengers button (shows "1 passenger" or similar)
        pax_btn = page.locator(
            'button[aria-label*="passenger"], '
            'button[aria-label*="traveller"], '
            '[data-travel-service="passengers"]'
        ).first
        if await pax_btn.count() == 0:
            # Fallback: look for an element showing current passenger count
            pax_btn = page.locator(
                'div[jsname="kQxpnb"], '
                '[aria-label*="1 passenger"]'
            ).first

        if await pax_btn.count() == 0:
            print("[warn] Passengers button not found, skipping", file=sys.stderr)
            return

        await pax_btn.click()
        await asyncio.sleep(0.8)

        # Scope to the opened dialog / popover
        dialog = page.locator('[role="dialog"]:visible, [role="menu"]:visible').first
        if await dialog.count() == 0:
            dialog = page  # fallback to full page

        async def click_n(label_fragment, times):
            """Click the +/- button matching label_fragment, `times` times."""
            if times == 0:
                return
            btn = dialog.locator(f'button[aria-label*="{label_fragment}"]').first
            if await btn.count() == 0:
                print(f"[warn] Button '{label_fragment}' not found", file=sys.stderr)
                return
            for _ in range(abs(times)):
                await btn.click()
                await asyncio.sleep(0.2)

        # Adults: default is 1, click + (adults-1) times or - if somehow <1
        adult_delta = adults - 1
        if adult_delta > 0:
            await click_n("Increase number of Adults", adult_delta)
        elif adult_delta < 0:
            await click_n("Decrease number of Adults", abs(adult_delta))

        await click_n("Increase number of Children", children)
        await click_n("Increase number of Infants in seat", infants_in_seat)
        await click_n("Increase number of Infants on lap", infants_on_lap)

        # Close the dialog by clicking Done
        done = dialog.locator('button:has-text("Done")').first
        if await done.count() > 0:
            await done.click()
        else:
            await page.keyboard.press("Escape")
        await asyncio.sleep(0.5)

    except Exception as e:
        print(f"[warn] set_passengers failed: {e}", file=sys.stderr)


async def fill_airport(page, field_label, iata_code):
    """Fill an origin or destination airport field."""
    label_candidates = {
        "origin": ["Where from?", "Origin", "From"],
        "destination": ["Where to?", "Destination", "To"],
    }
    for label in label_candidates.get(field_label, [field_label]):
        inp = page.locator(
            f'input[aria-label="{label}"], [placeholder="{label}"]'
        ).first
        if await inp.count() > 0:
            await inp.click()
            await asyncio.sleep(0.3)
            await inp.fill("")
            await inp.type(iata_code, delay=80)
            await asyncio.sleep(1.5)
            suggestion = page.locator('[role="option"]').first
            if await suggestion.count() > 0:
                await suggestion.click()
                await asyncio.sleep(0.5)
                return True
    return False


async def fill_date(page, date_str, field_label="Departure"):
    """Fill a date field (YYYY-MM-DD)."""
    date_inp = page.locator(
        f'input[aria-label*="{field_label}"], '
        f'input[placeholder*="{field_label}"], '
        f'[aria-label*="{field_label}"][role="textbox"]'
    ).first

    if await date_inp.count() > 0:
        await date_inp.click()
        await asyncio.sleep(0.5)
        await date_inp.fill("")
        dt = datetime.strptime(date_str, "%Y-%m-%d")
        formatted = dt.strftime("%a, %b %-d")
        await date_inp.type(formatted, delay=60)
        await asyncio.sleep(1)
        done = page.locator('button:has-text("Done"), button[aria-label="Done"]').first
        if await done.count() > 0:
            await done.click()
        else:
            await date_inp.press("Enter")
        await asyncio.sleep(0.5)
        return True
    return False


async def extract_results(page):
    """Extract flight result cards from the loaded page."""
    results = []
    try:
        cards = await page.query_selector_all('[data-iid="recPrimary"], [jsname="IWWDBc"]')
        if not cards:
            cards = await page.query_selector_all('li[class*="pIav2d"], [role="listitem"]')

        for card in cards[:20]:
            try:
                text = await card.inner_text()
                lines = [l.strip() for l in text.split("\n") if l.strip()]
                if not lines:
                    continue
                result = {"raw": "\n".join(lines)}
                flight = {}
                for line in lines:
                    if any(c in line for c in ("£", "$", "€")):
                        flight["price"] = line.strip()
                    elif "nonstop" in line.lower() or "stop" in line.lower():
                        flight["stops"] = line.strip()
                    elif " – " in line and ":" in line:
                        flight["times"] = line.strip()
                    elif "hr" in line and "min" in line:
                        flight["duration"] = line.strip()
                if flight:
                    for line in lines:
                        if line not in flight.values() and len(line) > 2:
                            flight["airline"] = line
                            break
                    result.update(flight)
                results.append(result)
            except Exception:
                continue
    except Exception as e:
        print(f"[warn] Extraction error: {e}", file=sys.stderr)
    return results


async def search_flights(origin, destination, date, trip_type="one-way",
                         return_date=None, adults=1, children=0,
                         infants_in_seat=0, infants_on_lap=0, cabin="economy"):
    async with AsyncCamoufox(
        headless=True, proxy=PROXY, geoip=True,
        viewport={"width": 1280, "height": 900}
    ) as browser:
        page = await browser.new_page()

        print("[info] Loading Google Flights...", file=sys.stderr)
        await page.goto(
            "https://www.google.com/travel/flights?hl=en&gl=GB&curr=GBP",
            wait_until="domcontentloaded", timeout=30000,
        )
        await asyncio.sleep(3)
        await page.screenshot(path="/tmp/google-flights-1-load.png")

        await dismiss_consent(page)
        await asyncio.sleep(1)

        print(f"[info] Setting trip type: {trip_type}", file=sys.stderr)
        await set_trip_type(page, trip_type)
        await asyncio.sleep(0.5)

        total_pax = adults + children + infants_in_seat + infants_on_lap
        print(f"[info] Setting passengers: {adults}A {children}C {infants_in_seat}IS {infants_on_lap}IL",
              file=sys.stderr)
        await set_passengers(page, adults, children, infants_in_seat, infants_on_lap)

        print(f"[info] Filling origin: {origin}", file=sys.stderr)
        await fill_airport(page, "origin", origin)

        print(f"[info] Filling destination: {destination}", file=sys.stderr)
        await fill_airport(page, "destination", destination)
        await page.screenshot(path="/tmp/google-flights-2-airports.png")

        print(f"[info] Filling departure date: {date}", file=sys.stderr)
        await fill_date(page, date, "Departure")
        if trip_type == "return" and return_date:
            print(f"[info] Filling return date: {return_date}", file=sys.stderr)
            await fill_date(page, return_date, "Return")
        await page.screenshot(path="/tmp/google-flights-3-dates.png")

        search_btn = page.locator(
            'button[aria-label*="Search"], button:has-text("Search"), '
            'button[jsname="vLv7Lb"]'
        ).first
        if await search_btn.count() > 0:
            await search_btn.click()
        else:
            await page.keyboard.press("Enter")

        print("[info] Waiting for results...", file=sys.stderr)
        try:
            await page.wait_for_selector("[data-iid='recPrimary']", timeout=20000)
        except Exception:
            print("[warn] Timed out waiting for recPrimary, continuing anyway", file=sys.stderr)

        await asyncio.sleep(4)
        await page.screenshot(path="/tmp/google-flights-4-results.png", full_page=True)

        return await extract_results(page)


def main():
    parser = argparse.ArgumentParser(description="Search Google Flights via camoufox")
    parser.add_argument("--origin", required=True, help="Origin IATA code (e.g. LGW)")
    parser.add_argument("--destination", required=True, help="Destination IATA code (e.g. PMI)")
    parser.add_argument("--date", required=True, help="Departure date YYYY-MM-DD")
    parser.add_argument("--trip-type", default="one-way", choices=["one-way", "return"])
    parser.add_argument("--return-date", help="Return date YYYY-MM-DD (return trips)")
    parser.add_argument("--adults", type=int, default=1, help="Number of adult passengers")
    parser.add_argument("--children", type=int, default=0, help="Children age 2-11")
    parser.add_argument("--infants-in-seat", type=int, default=0, help="Infants with own seat")
    parser.add_argument("--infants-on-lap", type=int, default=0, help="Lap infants age 0-1")
    parser.add_argument("--cabin", default="economy",
                        choices=["economy", "premium_economy", "business", "first"])
    # Legacy alias
    parser.add_argument("--passengers", type=int, default=None,
                        help="(legacy) Sets --adults; use --adults instead")
    args = parser.parse_args()

    adults = args.adults
    if args.passengers is not None:
        adults = args.passengers  # backwards compat

    results = asyncio.run(search_flights(
        origin=args.origin,
        destination=args.destination,
        date=args.date,
        trip_type=args.trip_type,
        return_date=args.return_date,
        adults=adults,
        children=args.children,
        infants_in_seat=args.infants_in_seat,
        infants_on_lap=args.infants_on_lap,
        cabin=args.cabin,
    ))

    print(json.dumps(results, indent=2, ensure_ascii=False))
    print(f"\n[info] {len(results)} results. Screenshots at /tmp/google-flights-*.png",
          file=sys.stderr)


if __name__ == "__main__":
    main()
