#!/usr/bin/env python3
"""
GHL OAuth Private App Creator v2
Creates "Genesis CRM Bridge" app on marketplace.gohighlevel.com
Improved: better login flow, SPA-aware navigation, domcontentloaded wait strategy.
"""

import json
import time
import sys
import os
from pathlib import Path
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout

# Configuration
MARKETPLACE_URL = "https://marketplace.gohighlevel.com"
EMAIL = "kinan@agileadapt.com"
PASSWORD = "SystemBird505*"
APP_NAME = "Genesis CRM Bridge"
REDIRECT_URI = "https://api.sunaivadigital.com/api/ghl/oauth/callback"
SCREENSHOT_DIR = Path("/mnt/e/genesis-system/GHL/oauth/screenshots")
CREDENTIALS_FILE = Path("/mnt/e/genesis-system/GHL/oauth/app_credentials.json")

REQUIRED_SCOPES = [
    "contacts.readonly", "contacts.write",
    "conversations.readonly", "conversations.write",
    "conversations/message.readonly", "conversations/message.write",
    "calendars.readonly", "calendars.write",
    "calendars/events.readonly", "calendars/events.write",
    "opportunities.readonly", "opportunities.write",
    "locations.readonly", "locations.write",
    "locations/customFields.readonly", "locations/customFields.write",
    "locations/customValues.readonly", "locations/customValues.write",
    "locations/tags.readonly", "locations/tags.write",
    "workflows.readonly",
    "oauth.readonly",
    "oauth.write",
]

step_num = [0]

def ss(page, name):
    """Take screenshot."""
    step_num[0] += 1
    path = SCREENSHOT_DIR / f"{step_num[0]:02d}_{name}.png"
    page.screenshot(path=str(path), full_page=False)
    print(f"  [SS] {path.name}")
    return path

def dump_page_info(page):
    """Print current URL and page title."""
    print(f"  URL: {page.url}")
    try:
        title = page.title()
        print(f"  Title: {title}")
    except:
        pass

def main():
    SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True)

    print("=" * 70)
    print("GHL OAuth App Creator v2 — Genesis CRM Bridge")
    print("=" * 70)

    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=True,
            args=[
                "--no-sandbox",
                "--disable-blink-features=AutomationControlled",
                "--disable-web-security",
            ]
        )
        context = browser.new_context(
            viewport={"width": 1920, "height": 1080},
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
            ignore_https_errors=True,
        )
        page = context.new_page()
        page.set_default_timeout(30000)

        try:
            # ===== STEP 1: LOAD MARKETPLACE =====
            print("\n[STEP 1] Loading marketplace...")
            page.goto(MARKETPLACE_URL, wait_until="domcontentloaded", timeout=60000)
            page.wait_for_timeout(5000)  # Let SPA render
            ss(page, "marketplace_loaded")
            dump_page_info(page)

            # ===== STEP 2: CLICK SIGN IN =====
            print("\n[STEP 2] Clicking Sign In...")
            # Click the "Sign In" link in the top nav
            sign_in = page.locator('text="Sign In"').first
            if sign_in.is_visible():
                sign_in.click()
                print("  Clicked 'Sign In'")
                page.wait_for_timeout(5000)
                ss(page, "sign_in_clicked")
                dump_page_info(page)
            else:
                print("  'Sign In' not visible, trying alternative...")
                # Try going directly to login URL
                page.goto("https://marketplace.gohighlevel.com/login", wait_until="domcontentloaded", timeout=30000)
                page.wait_for_timeout(3000)
                ss(page, "direct_login_page")
                dump_page_info(page)

            # ===== STEP 3: FILL LOGIN FORM =====
            print("\n[STEP 3] Filling login form...")

            # Wait for email input to appear
            try:
                page.wait_for_selector('input[type="email"], input[name="email"], input[placeholder*="email" i], input[placeholder*="Email" i]', timeout=15000)
            except PlaywrightTimeout:
                print("  No email input found, dumping page HTML...")
                html = page.content()
                # Save HTML for debugging
                with open(SCREENSHOT_DIR / "login_page.html", "w") as f:
                    f.write(html)
                print(f"  Saved HTML ({len(html)} chars) to login_page.html")
                ss(page, "no_email_input")
                # Try broader selector
                all_inputs = page.locator('input').all()
                print(f"  Found {len(all_inputs)} input elements total")
                for i, inp in enumerate(all_inputs):
                    try:
                        inp_type = inp.get_attribute("type") or "?"
                        inp_name = inp.get_attribute("name") or "?"
                        inp_placeholder = inp.get_attribute("placeholder") or "?"
                        print(f"    input[{i}]: type={inp_type} name={inp_name} placeholder={inp_placeholder}")
                    except:
                        print(f"    input[{i}]: (error reading attributes)")

            # Fill email
            email_input = page.locator('input[type="email"], input[name="email"], input[placeholder*="email" i]').first
            if email_input.is_visible():
                email_input.fill(EMAIL)
                print(f"  Filled email: {EMAIL}")
            else:
                # Try first visible text/email input
                inputs = page.locator('input:visible').all()
                if inputs:
                    inputs[0].fill(EMAIL)
                    print(f"  Filled first visible input with email")

            page.wait_for_timeout(500)

            # Fill password
            password_input = page.locator('input[type="password"]').first
            if password_input.is_visible():
                password_input.fill(PASSWORD)
                print("  Filled password")

            page.wait_for_timeout(500)
            ss(page, "credentials_filled")

            # ===== STEP 4: SUBMIT LOGIN =====
            print("\n[STEP 4] Submitting login...")

            # Find and click submit
            submit = page.locator('button[type="submit"], button:has-text("Sign"), button:has-text("Log In")').first
            if submit.is_visible():
                submit.click()
                print("  Clicked submit button")
            else:
                # Try pressing Enter on password field
                password_input.press("Enter")
                print("  Pressed Enter on password field")

            # Wait for navigation / login to complete
            page.wait_for_timeout(8000)
            ss(page, "after_login")
            dump_page_info(page)

            # Check for login errors
            error_msgs = page.locator('.error, .alert-danger, [class*="error"], [role="alert"]').all()
            for em in error_msgs:
                if em.is_visible():
                    print(f"  WARNING - Error message: {em.text_content()}")

            # ===== STEP 5: NAVIGATE TO MY APPS =====
            print("\n[STEP 5] Navigating to My Apps...")

            # After login, the URL might change. Try navigating to apps page.
            # First check if there's a "My Apps" link in the nav
            my_apps = page.locator('text="My Apps"').first
            if my_apps.is_visible():
                my_apps.click()
                print("  Clicked 'My Apps'")
                page.wait_for_timeout(5000)
            else:
                # Try direct navigation
                page.goto("https://marketplace.gohighlevel.com/apps", wait_until="domcontentloaded", timeout=60000)
                page.wait_for_timeout(5000)

            ss(page, "my_apps_page")
            dump_page_info(page)

            # If there's still no apps page, try other URLs
            if "apps" not in page.url.lower():
                alt_urls = [
                    "https://marketplace.gohighlevel.com/apps/list",
                    "https://marketplace.gohighlevel.com/my-apps",
                    "https://marketplace.gohighlevel.com/dashboard",
                ]
                for url in alt_urls:
                    print(f"  Trying: {url}")
                    page.goto(url, wait_until="domcontentloaded", timeout=30000)
                    page.wait_for_timeout(3000)
                    if "apps" in page.url.lower() or "dashboard" in page.url.lower():
                        break
                ss(page, "apps_alt_url")
                dump_page_info(page)

            # ===== STEP 6: CREATE APP =====
            print("\n[STEP 6] Looking for Create App button...")

            create_btn = page.locator('button:has-text("Create App"), a:has-text("Create App"), text="Create App"').first
            if create_btn.is_visible():
                create_btn.click()
                print("  Clicked 'Create App'")
                page.wait_for_timeout(5000)
                ss(page, "create_app_form")
                dump_page_info(page)
            else:
                # Look for any create/add button
                alt_btns = page.locator('button:has-text("Create"), button:has-text("New"), button:has-text("Add"), a:has-text("Create")').all()
                for btn in alt_btns:
                    if btn.is_visible():
                        txt = btn.text_content().strip()
                        print(f"  Found button: '{txt}'")
                        btn.click()
                        page.wait_for_timeout(5000)
                        break
                ss(page, "create_btn_search")
                dump_page_info(page)

            # ===== STEP 7: FILL APP DETAILS =====
            print("\n[STEP 7] Filling app details...")

            # App Name
            name_input = page.locator('input[name="name"], input[placeholder*="name" i], input[placeholder*="Name" i]').first
            if not name_input.is_visible():
                # Try all text inputs
                text_inputs = page.locator('input[type="text"], input:not([type])').all()
                for ti in text_inputs:
                    if ti.is_visible():
                        name_input = ti
                        break

            if name_input.is_visible():
                name_input.fill(APP_NAME)
                print(f"  Filled name: {APP_NAME}")
            else:
                print("  WARNING: No name input found")
                # Dump all visible inputs
                vis_inputs = page.locator('input:visible').all()
                print(f"  Visible inputs: {len(vis_inputs)}")
                for i, vi in enumerate(vis_inputs):
                    t = vi.get_attribute("type") or "?"
                    n = vi.get_attribute("name") or "?"
                    ph = vi.get_attribute("placeholder") or "?"
                    print(f"    [{i}] type={t} name={n} placeholder={ph}")

            page.wait_for_timeout(1000)
            ss(page, "name_filled")

            # Look for "Private" option (radio, button, or in a form)
            print("  Looking for Private app type...")
            # Try multiple strategies
            private_selectors = [
                'label:has-text("Private")',
                'text="Private"',
                'input[value="private" i]',
                'div:has-text("Private")',
                'span:has-text("Private")',
                'button:has-text("Private")',
            ]
            for sel in private_selectors:
                try:
                    elem = page.locator(sel).first
                    if elem.is_visible():
                        elem.click()
                        print(f"  Clicked Private option via: {sel}")
                        break
                except:
                    continue

            page.wait_for_timeout(1000)

            # Distribution Type: Agency
            print("  Looking for Agency distribution type...")
            agency_selectors = [
                'label:has-text("Agency")',
                'text="Agency"',
                'input[value="agency" i]',
                'div:has-text("Agency")',
                'span:has-text("Agency")',
            ]
            for sel in agency_selectors:
                try:
                    elem = page.locator(sel).first
                    if elem.is_visible():
                        elem.click()
                        print(f"  Clicked Agency option via: {sel}")
                        break
                except:
                    continue

            page.wait_for_timeout(1000)
            ss(page, "app_details_filled")

            # ===== STEP 8: REDIRECT URI =====
            print("\n[STEP 8] Setting redirect URI...")

            redirect_input = page.locator('input[placeholder*="redirect" i], input[placeholder*="URI" i], input[placeholder*="callback" i], input[name*="redirect" i]').first
            if redirect_input.is_visible():
                redirect_input.fill(REDIRECT_URI)
                print(f"  Filled redirect URI: {REDIRECT_URI}")
            else:
                print("  Redirect URI input not found yet (may be on next step/page)")
                # Try finding any URL-like input
                url_inputs = page.locator('input[type="url"], input[placeholder*="http"]').all()
                for ui in url_inputs:
                    if ui.is_visible():
                        ui.fill(REDIRECT_URI)
                        print(f"  Filled URL input with redirect URI")
                        break

            page.wait_for_timeout(1000)
            ss(page, "redirect_uri")

            # ===== STEP 9: SELECT SCOPES =====
            print("\n[STEP 9] Selecting scopes...")

            # Look for a scopes section/tab/accordion
            scopes_tab = page.locator('text="Scopes", a:has-text("Scopes"), button:has-text("Scopes"), [data-tab*="scope" i]').first
            try:
                if scopes_tab.is_visible(timeout=3000):
                    scopes_tab.click()
                    print("  Clicked Scopes tab/section")
                    page.wait_for_timeout(3000)
            except:
                print("  No explicit Scopes tab found")

            ss(page, "scopes_section")

            # Check for scope checkboxes
            selected = 0
            missing = []

            for scope in REQUIRED_SCOPES:
                found = False
                # Try: exact text match, checkbox value, label
                strategies = [
                    f'input[value="{scope}"]',
                    f'label:has-text("{scope}")',
                    f'span:has-text("{scope}")',
                    f'text="{scope}"',
                    f'div:has-text("{scope}")',
                ]
                for sel in strategies:
                    try:
                        elem = page.locator(sel).first
                        if elem.is_visible(timeout=1000):
                            # Check if it's an input checkbox
                            tag = elem.evaluate("el => el.tagName")
                            if tag == "INPUT":
                                if not elem.is_checked():
                                    elem.check()
                            else:
                                elem.click()
                            found = True
                            selected += 1
                            break
                    except:
                        continue

                if found:
                    print(f"  [+] {scope}")
                else:
                    missing.append(scope)

                page.wait_for_timeout(200)

            print(f"\n  Selected: {selected}/{len(REQUIRED_SCOPES)}")
            if missing:
                print(f"  Missing: {missing}")

            ss(page, "scopes_selected")

            # Scroll down to see more
            page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
            page.wait_for_timeout(1000)
            ss(page, "scopes_scrolled")

            # ===== STEP 10: SAVE/CREATE =====
            print("\n[STEP 10] Saving app...")

            save_btn = page.locator('button:has-text("Save"), button:has-text("Create"), button[type="submit"]').first
            if save_btn.is_visible():
                save_btn.click()
                print("  Clicked Save/Create")
                page.wait_for_timeout(8000)
                ss(page, "app_saved")
                dump_page_info(page)
            else:
                print("  WARNING: No Save/Create button found")
                ss(page, "no_save_btn")

            # ===== STEP 11: CAPTURE CREDENTIALS =====
            print("\n[STEP 11] Capturing credentials...")
            ss(page, "credentials_page")

            # Get all page text for manual extraction
            page_text = page.evaluate("document.body.innerText")

            # Try to find client ID and secret
            client_id = None
            client_secret = None

            # Look for labeled fields
            for label_text in ["Client ID", "Client Id", "clientId", "client_id"]:
                try:
                    label = page.locator(f'text="{label_text}"').first
                    if label.is_visible(timeout=2000):
                        # Get the value from nearby element
                        parent = label.locator('..')
                        value_elem = parent.locator('input, code, span, p').first
                        if value_elem.is_visible():
                            val = value_elem.input_value() if value_elem.evaluate("el => el.tagName") == "INPUT" else value_elem.text_content()
                            if val and val.strip() != label_text:
                                client_id = val.strip()
                                print(f"  Found Client ID: {client_id}")
                                break
                except:
                    continue

            for label_text in ["Client Secret", "Client secret", "clientSecret", "client_secret"]:
                try:
                    label = page.locator(f'text="{label_text}"').first
                    if label.is_visible(timeout=2000):
                        parent = label.locator('..')
                        value_elem = parent.locator('input, code, span, p').first
                        if value_elem.is_visible():
                            val = value_elem.input_value() if value_elem.evaluate("el => el.tagName") == "INPUT" else value_elem.text_content()
                            if val and val.strip() != label_text:
                                client_secret = val.strip()
                                print(f"  Found Client Secret: {client_secret}")
                                break
                except:
                    continue

            # Save everything
            creds = {
                "app_name": APP_NAME,
                "client_id": client_id,
                "client_secret": client_secret,
                "redirect_uri": REDIRECT_URI,
                "scopes_selected": selected,
                "scopes_missing": missing,
                "scopes_total": len(REQUIRED_SCOPES),
                "created_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
                "page_url": page.url,
                "page_text_excerpt": page_text[:3000] if page_text else None,
            }

            with open(CREDENTIALS_FILE, "w") as f:
                json.dump(creds, f, indent=2)
            print(f"\n  Saved to: {CREDENTIALS_FILE}")

            # Final screenshot
            ss(page, "final")

            print("\n" + "=" * 70)
            print("RESULT")
            print("=" * 70)
            print(f"  Client ID:     {client_id or 'NOT CAPTURED - check screenshots'}")
            print(f"  Client Secret: {client_secret or 'NOT CAPTURED - check screenshots'}")
            print(f"  Scopes:        {selected}/{len(REQUIRED_SCOPES)}")
            print(f"  Screenshots:   {SCREENSHOT_DIR}")
            print(f"  Creds file:    {CREDENTIALS_FILE}")

        except Exception as e:
            print(f"\nFATAL ERROR: {e}")
            import traceback
            traceback.print_exc()
            ss(page, "FATAL_ERROR")

            # Save debug info
            try:
                html = page.content()
                with open(SCREENSHOT_DIR / "error_page.html", "w") as f:
                    f.write(html)
                print(f"  Saved error page HTML")
            except:
                pass
        finally:
            browser.close()

if __name__ == "__main__":
    main()
