"""
Xero Supporting Documents helper functions.

Contains the handler for the "Get Support Docs" flow used during recording.
This module avoids any UI clicking or playback-like behavior during recording;
it only records a subplaylist action for later playback.
"""

import logging
import tkinter as tk
from tkinter import ttk, messagebox
import os
import time
import base64
import json


def _resolve_local_playlist_id(app) -> int | None:
    """Resolve the current local SQLite playlist id for the selected playlist name."""
    try:
        local_pl_id = getattr(app, 'current_local_playlist_id', None)
        if local_pl_id is not None:
            return int(local_pl_id)
    except Exception:
        pass
    try:
        import db
        conn = db.get_connection()
        cur = conn.cursor()
        cur.execute('SELECT id FROM Playlists WHERE name = ?', (app.selected_playlist.get(),))
        row = cur.fetchone()
        conn.close()
        return int(row[0]) if row else None
    except Exception:
        return None


def add_subplaylist_action(app) -> None:
    """Add a subplaylist action for the Get Support Docs flow during recording.

    - Requires an active recording
    - Always show a dropdown of same-application playlists to choose from
    """
    if not getattr(app, 'recording', False):
        try:
            app.status_var.set('Must be recording to add subplaylist action')
        except Exception:
            pass
        return

    # No configured id: let the user pick a subplaylist from same application
    try:
        current_app_id = getattr(app, 'current_application_id', None)
        if current_app_id is None:
            current_app_id = getattr(app, 'application_id', None)
        current_app_id_int = int(current_app_id) if current_app_id is not None else None
    except Exception:
        current_app_id_int = None

    try:
        from mysql.mysql_client import (
            get_playlist_names_with_app_details,
            get_playlist_id_by_name,
        )
        rows = get_playlist_names_with_app_details() or []
    except Exception as e:
        logging.error(f"[SupportDocs] Failed to load playlists: {e}")
        messagebox.showerror("Error", f"Failed to load playlists: {e}")
        return

    # Filter to same application (when available)
    available_playlists: list[str] = []
    for row in rows:
        try:
            app_id = row.get('application_id') if isinstance(row, dict) else None
            name = row.get('name') if isinstance(row, dict) else None
            if name is None:
                continue
            if current_app_id_int is None or app_id == current_app_id_int:
                available_playlists.append(name)
        except Exception:
            continue

    # Exclude currently selected playlist to avoid recursion
    try:
        current_pl_name = app.selected_playlist.get()
        available_playlists = [n for n in available_playlists if n != current_pl_name]
    except Exception:
        pass

    if not available_playlists:
        messagebox.showwarning("No Playlists", "No matching playlists available for this application")
        return

    # Dialog to choose subplaylist
    dialog = tk.Toplevel(app)
    try:
        app._subplaylist_dialog = dialog  # Expose so recorder can suppress clicks
    except Exception:
        pass
    dialog.title('Add Subplaylist Action')
    dialog.geometry('300x160')
    dialog.transient(app)
    dialog.attributes('-topmost', True)
    dialog.focus_force()
    dialog.grab_set()

    tk.Label(dialog, text='Select subplaylist:', font=('Segoe UI', 10)).pack(pady=(15,5))
    sel_var = tk.StringVar(value=available_playlists[0])
    combo = ttk.Combobox(dialog, textvariable=sel_var, values=available_playlists, state='readonly')
    combo.pack(padx=20, fill='x')
    combo.current(0)

    btn_frame = tk.Frame(dialog)
    btn_frame.pack(pady=15)

    selected = {'name': None}

    def on_ok():
        selected['name'] = sel_var.get()
        try:
            dialog.destroy()
        finally:
            try:
                app._subplaylist_dialog = None
            except Exception:
                pass

    def on_cancel():
        selected['name'] = None
        try:
            dialog.destroy()
        finally:
            try:
                app._subplaylist_dialog = None
            except Exception:
                pass

    tk.Button(btn_frame, text='OK', width=10, command=on_ok).pack(side='left', padx=5)
    tk.Button(btn_frame, text='Cancel', width=10, command=on_cancel).pack(side='left', padx=5)

    try:
        dialog.protocol("WM_DELETE_WINDOW", on_cancel)
    except Exception:
        pass
    dialog.wait_window(dialog)
    subplaylist_name = selected['name']

    if not subplaylist_name or subplaylist_name not in available_playlists:
        return  # cancelled or invalid

    try:
        subplaylist_id = get_playlist_id_by_name(subplaylist_name)
        if subplaylist_id:
            import recorder
            local_pl_id = _resolve_local_playlist_id(app)
            if local_pl_id is not None:
                recorder.record_subplaylist_action(local_pl_id, subplaylist_id)
                try:
                    app.status_var.set(f'Added subplaylist action: {subplaylist_name}')
                except Exception:
                    pass
            else:
                messagebox.showerror("Error", "No active playlist for recording")
        else:
            messagebox.showerror("Error", f"Playlist '{subplaylist_name}' not found")
    except Exception as e:
        logging.error(f"[SupportDocs] Failed to add subplaylist action: {e}")
        messagebox.showerror("Error", f"Failed to add subplaylist action: {e}")



def _xero_links_prompt(*args, **kwargs):
    """Xero-specific prompt for supporting-document links analysis during playback."""
    try:
        screen_width = int(kwargs.get('screen_width') or (args[0] if len(args) > 0 else 1920))
    except Exception:
        screen_width = 1920
    try:
        screen_height = int(kwargs.get('screen_height') or (args[1] if len(args) > 1 else 1080))
    except Exception:
        screen_height = 1080
    sw = screen_width; sh = screen_height
    return (
        """
            Analyze this screenshot from Xero and identify ONLY supporting document links related to invoices or bills.

            Look for:
            - Download links for PDFs, receipts, or supporting documents
            - "View" or "Download" buttons for attachments
            - Links to related documents or evidence (e.g., receipt, payment, statement)
            - File attachment icons (paperclip, document icons), PDF icons
            - Any clickable elements that appear to open document evidence

            Return ONLY a JSON array with this exact format:
            [
                {{
                    "type": "supporting_document_link",
                    "description": "brief description of what the link provides",
                    "link_words": "the exact visible text of the link (verbatim)",
                    "coordinates": [x, y],
                    "confidence": 0.95,
                    "button": true
                }}
            ]

            Rules:
            - link_words MUST be the exact words in the clickable link, verbatim, including punctuation
            - If multiple words compose the link, include the full visible phrase as link_words
            - If no link text is visible (icon-only), set link_words to "" (empty string)
            - button: set to true if the element appears to be a clickable button or icon, false for plain text links
            - x and y must be pixel coordinates (0-{xmax} for x, 0-{ymax} for y)
            - ONLY include links for supporting documents; exclude actions like "Attach files", "Upload", or print actions
            - Be precise with coordinates (center of the clickable element)
            - Return valid JSON only
            - This screenshot is {sw}x{sh} pixels
        """
    ).format(xmax=sw-1, ymax=sh-1, sw=sw, sh=sh)


def play_supporting_docs(app) -> None:
    """Xero-specific capture + analyze + click flow for supporting documents during playback."""
    try:
        app.show_loader("Analyzing supporting documents...")
        try:
            wait_time = float(os.getenv("SCREENSHOT_PRE_DELAY_SEC", "5.0"))
        except Exception:
            wait_time = 1.0
        time.sleep(wait_time)

        # Heartbeat before heavy capture/analysis begins
        try:
            hb_interval = float(os.getenv("HB_INTERVAL_SEC", "60"))
            app.send_heartbeat_throttled(min_interval_sec=hb_interval)
        except Exception:
            pass

        # Prefer the app's analyzer capture
        analyzer = getattr(app, 'screenshot_manager', None)
        analyzer = getattr(analyzer, 'openai_analyzer', None)
        if analyzer is None:
            app.hide_loader(); app.status_var.set("Analyzer unavailable")
            return

        screenshot_path = analyzer.capture_full_resolution_screenshot()
        if not screenshot_path:
            app.hide_loader(); app.status_var.set("Failed to capture screenshot")
            return

        # Build Xero-specific links prompt
        sw, sh = analyzer._get_screen_size()
        prompt = _xero_links_prompt(screen_width=sw, screen_height=sh)

        # Prepare image as data URL
        with open(screenshot_path, 'rb') as f:
            img_b = f.read()
        img_b64 = base64.b64encode(img_b).decode('utf-8')
        image_url = f"data:image/png;base64,{img_b64}"

        app.status_var.set("Analyzing with OpenAI...")
        # Heartbeat before OpenAI analysis
        try:
            hb_interval = float(os.getenv("HB_INTERVAL_SEC", "60"))
            app.send_heartbeat_throttled(min_interval_sec=hb_interval)
        except Exception:
            pass

        # Call unified vision helper and parse
        try:
            from .constants import OPENAI_VISION_TEMPERATURE
        except Exception:
            OPENAI_VISION_TEMPERATURE = 0.0
        try:
            content = analyzer._post_vision(
                prompt,
                image_url,
                max_tokens=2000,
                temperature=float(OPENAI_VISION_TEMPERATURE),
            )
        except Exception as _e:
            try:
                analyzer.openai_logger.error(f"OpenAI API error: {_e}")
            except Exception:
                pass
            app.hide_loader(); app.status_var.set("Analyze failed")
            return

        # Parse JSON array from response (reuse analyzer validation rules)
        try:
            start_idx = content.find('['); end_idx = content.rfind(']') + 1
            if start_idx != -1 and end_idx != -1:
                json_str = content[start_idx:end_idx]
                clickable_links = json.loads(json_str)
            else:
                clickable_links = []
        except Exception:
            clickable_links = []

        # Validate and normalize fields
        validated_links = []
        for link in (clickable_links or []):
            try:
                if isinstance(link, dict) and 'link_words' not in link:
                    desc = str(link.get('description') or '').strip()
                    link['link_words'] = desc
            except Exception:
                try:
                    link['link_words'] = ''
                except Exception:
                    pass
            if 'button' not in link:
                link['button'] = False
            if analyzer._validate_coordinates(link.get('coordinates', [])):
                validated_links.append(link)

        if not validated_links:
            app.hide_loader(); app.status_var.set("No supporting document links found")
            return

        # Update UI then perform clicking
        app.hide_loader()
        app.show_loader(f"Found {len(validated_links)} supporting document links. Starting automated clicking...")
        try:
            # Heartbeat before long clicking sequence
            hb_interval = float(os.getenv("HB_INTERVAL_SEC", "60"))
            app.send_heartbeat_throttled(min_interval_sec=hb_interval)
        except Exception:
            pass
        try:
            analyzer._perform_automated_link_clicking(
                original_screenshot_path=screenshot_path,
                clickable_links=validated_links,
                playlist_name=None,
            )
        except Exception:
            # Fallback to legacy clicker if present
            try:
                analyzer.perform_automated_clicking(validated_links, screenshot_path)
            except Exception:
                pass

        # Completion message
        app.hide_loader()
        app.status_var.set(f"Analysis complete! Processed {len(validated_links)} links")
    except Exception as e:
        try:
            app.hide_loader()
        except Exception:
            pass
        try:
            logging.error(f"[SupportDocs] Xero playback flow failed: {e}")
        except Exception:
            pass



def play_supporting_docs_attach_files(app) -> None:
    """Xero-specific 'Get Supporting Docs' flow that opens the Attach files/Files dialog and
    selects a subplaylist to run based on detected file count. This wraps the playback logic
    so callers only need the `app` instance.
    """
    # Capture one screenshot before all checks to reuse for Payment, Batch, and AttachFiles checks
    screenshot_path = None
    try:
        analyzer = getattr(app, 'screenshot_manager', None)
        analyzer = getattr(analyzer, 'openai_analyzer', None)
        if analyzer is not None:
            screenshot_path = analyzer.capture_full_resolution_screenshot()
    except Exception:
        pass

    # Pre-check for Payment via Rekognition; if found, run alternate playlist and return
    try:
        if run_payment_precheck(app, screenshot_path=screenshot_path):
            return
    except Exception:
        pass
    # Pre-check for Batch Deposit via Rekognition; if found, run alternate playlist and return
    try:
        if run_batch_deposit_precheck(app, screenshot_path=screenshot_path):
            return
    except Exception:
        pass

    try:
        from .playback import PlaybackManager
    except Exception:
        logging.exception("[SupportDocs] Failed to import PlaybackManager for Xero attach-files flow")
        return

    mgr = None
    try:
        mgr = PlaybackManager(app)
        _run_xero_attach_files_flow(mgr, screenshot_path=screenshot_path)
    except Exception:
        logging.exception("[SupportDocs] Xero attach-files flow failed")


def _run_xero_attach_files_flow(playback_mgr, screenshot_path=None) -> None:
    """Delegate to `autoclicker.xero_util.attach_files_flow.run_xero_attach_files_flow`."""
    try:
        from .xero_util.attach_files_flow import run_xero_attach_files_flow

        run_xero_attach_files_flow(playback_mgr, screenshot_path=screenshot_path)
    except Exception:
        import logging as _logging

        _logging.exception("[SupportDocs] Xero attach-files flow delegate failed")


def run_batch_deposit_precheck(app, screenshot_path=None) -> bool:
    """Check the current screen with Rekognition for the phrase configured in
    `constants.BATCH_DEPOSIT_PHRASE`. If detected, run the playlist
    `constants.BATCH_DEPOSIT_PLAYLIST_ID` and return True to indicate the
    caller should skip the attachments flow.

    Args:
        app: Application instance
        screenshot_path: Optional screenshot path to reuse. If None, captures a new screenshot.

    Returns:
        bool: True if the alternate playlist was executed (match found), else False.
    """
    try:
        from .constants import (
            BATCH_DEPOSIT_PRECHECK_ENABLED,
            BATCH_DEPOSIT_PHRASE,
            BATCH_DEPOSIT_PLAYLIST_ID,
            AWS_REGION,
        )
    except Exception:
        return False

    # Feature flag and configuration validation
    try:
        if not bool(BATCH_DEPOSIT_PRECHECK_ENABLED):
            return False
        phrase = str(BATCH_DEPOSIT_PHRASE or "").strip()
        if not phrase:
            return False
        target_playlist_id = int(BATCH_DEPOSIT_PLAYLIST_ID)
        if target_playlist_id <= 0:
            # No playlist configured; do not run
            return False
    except Exception:
        return False

    # Get analyzer and capture current screen (only if not provided)
    try:
        analyzer = getattr(app, 'screenshot_manager', None)
        analyzer = getattr(analyzer, 'openai_analyzer', None)
        if analyzer is None:
            return False
        if screenshot_path is None:
            screenshot_path = analyzer.capture_full_resolution_screenshot()
        if not screenshot_path:
            return False
    except Exception:
        return False

    # Use Rekognition tiler for robust phrase search
    try:
        from PIL import Image as _Image
        import boto3 as _boto3
        from .rekognition_tiler import (
            find_text_coordinates_tiled as _tiler_find,
            build_tile_detect_text_cache as _build_cache,
            find_text_coordinates_from_tile_cache as _find_from_cache,
        )
    except Exception:
        return False

    try:
        rk = _boto3.client('rekognition', region_name=AWS_REGION)
        img = _Image.open(screenshot_path).convert('RGB')
        # Prefer an existing cache if present; else build once for this screen
        _bundle = None
        try:
            _bundle = getattr(analyzer, "_last_rekognition_tile_cache", None)
        except Exception:
            _bundle = None
        if isinstance(_bundle, dict) and _bundle.get("img") is not None and _bundle.get("cache") is not None:
            _cache = _bundle.get("cache")
            img = _bundle.get("img")  # reuse the same image object
        else:
            try:
                _cache = _build_cache(
                    rk,
                    img,
                    upscale=2.0,
                    overlap_frac=0.10,
                    cols=1,
                    rows=5,
                    debug_dir=None,
                )
                try:
                    # Persist for later flows (e.g., Payment/Attach files) while on the same page
                    setattr(analyzer, "_last_rekognition_tile_cache", {"img": img, "cache": _cache})
                except Exception:
                    pass
            except Exception:
                _cache = []
        # Build case-insensitive phrase regex tolerant of punctuation (e.g., colon)
        # We join escaped tokens with "\s+" without enforcing word boundaries around each token,
        # to avoid failing on tokens that include punctuation like ":".
        import re as _re
        tokens = [_re.escape(t) for t in phrase.split() if t]
        if not tokens:
            return False
        query = fr"(?i)" + "\\s+".join(tokens)
        try:
            logging.info(f"[BatchPrecheck] Rekognition tiler search for phrase='{phrase}' on '{screenshot_path}'")
        except Exception:
            pass
        coords = _find_from_cache(
            img,
            _cache,
            query,
            is_regex=True,
            require_include=True,
            stop_at_first_include=True,
            debug_dir=None,
        )
    except Exception:
        coords = None

    # If found, execute the alternate playlist and signal skip
    if isinstance(coords, (list, tuple)) and len(coords) == 2:
        try:
            from .playback import PlaybackManager
            mgr = PlaybackManager(app)
            # Ensure playback loop semantics so subplaylist actions execute
            try:
                if hasattr(app, 'status_var'):
                    app.status_var.set('Playing')
            except Exception:
                pass
            mgr._execute_playlist_by_id(int(target_playlist_id))
            try:
                logging.info(f"[BatchPrecheck] Phrase matched; executed playlist id={int(target_playlist_id)}")
            except Exception:
                pass
            # After running the alternate playlist, run the reusable post-playlist dialog check
            try:
                from .xero_util.attach_files_dialog_utils import run_post_playlist_dialog_check as _post_check
                _post_check(app)
            except Exception:
                pass
            return True
        except Exception:
            return False

    return False


def run_payment_precheck(app, screenshot_path=None) -> bool:
    """Check the current screen with Rekognition for `constants.PAYMENT_PHRASE`. If detected,
    run `constants.PAYMENT_PLAYLIST_ID`, then run the post-playlist dialog check. Returns True
    when matched and executed, else False.

    Args:
        app: Application instance
        screenshot_path: Optional screenshot path to reuse. If None, captures a new screenshot.
    """
    try:
        from .constants import (
            PAYMENT_PRECHECK_ENABLED,
            PAYMENT_PHRASE,
            SPEND_MONEY_PHRASE,
            PAYMENT_PLAYLIST_ID,
            AWS_REGION,
        )
    except Exception:
        return False

    try:
        if not bool(PAYMENT_PRECHECK_ENABLED):
            return False
        phrase = str(PAYMENT_PHRASE or "").strip()
        spend_phrase = str(SPEND_MONEY_PHRASE or "").strip()
        # Build the list of phrases to check; prioritize the original payment phrase
        phrases_to_check = [p for p in [phrase, spend_phrase] if p]
        if not phrases_to_check:
            return False
        target_playlist_id = int(PAYMENT_PLAYLIST_ID)
        if target_playlist_id <= 0:
            return False
    except Exception:
        return False

    try:
        analyzer = getattr(app, 'screenshot_manager', None)
        analyzer = getattr(analyzer, 'openai_analyzer', None)
        if analyzer is None:
            return False
        if screenshot_path is None:
            screenshot_path = analyzer.capture_full_resolution_screenshot()
        if not screenshot_path:
            return False
    except Exception:
        return False

    try:
        from PIL import Image as _Image
        import boto3 as _boto3
        from .rekognition_tiler import (
            find_text_coordinates_tiled as _tiler_find,
            build_tile_detect_text_cache as _build_cache,
            find_text_coordinates_from_tile_cache as _find_from_cache,
        )
    except Exception:
        return False

    try:
        rk = _boto3.client('rekognition', region_name=AWS_REGION)
        img = _Image.open(screenshot_path).convert('RGB')
        import re as _re
        coords = None
        # Prefer an existing cache if present; else build once for this screen
        _bundle = None
        try:
            _bundle = getattr(analyzer, "_last_rekognition_tile_cache", None)
        except Exception:
            _bundle = None
        if isinstance(_bundle, dict) and _bundle.get("img") is not None and _bundle.get("cache") is not None:
            _cache = _bundle.get("cache")
            img = _bundle.get("img")
        else:
            try:
                _cache = _build_cache(
                    rk,
                    img,
                    upscale=2.0,
                    overlap_frac=0.10,
                    cols=1,
                    rows=5,
                    debug_dir=None,
                )
                try:
                    setattr(analyzer, "_last_rekognition_tile_cache", {"img": img, "cache": _cache})
                except Exception:
                    pass
            except Exception:
                _cache = []
        for _phrase in phrases_to_check:
            tokens = [_re.escape(t) for t in _phrase.split() if t]
            if not tokens:
                continue
            query = fr"(?i)" + "\\s+".join(tokens)
            try:
                logging.info(f"[PaymentPrecheck] Rekognition tiler search for phrase='{_phrase}' on '{screenshot_path}'")
            except Exception:
                pass
            coords = _find_from_cache(
                img,
                _cache,
                query,
                is_regex=True,
                require_include=True,
                stop_at_first_include=True,
                debug_dir=None,
            )
            if isinstance(coords, (list, tuple)) and len(coords) == 2:
                break
    except Exception:
        coords = None

    if isinstance(coords, (list, tuple)) and len(coords) == 2:
        try:
            from .playback import PlaybackManager
            mgr = PlaybackManager(app)
            try:
                if hasattr(app, 'status_var'):
                    app.status_var.set('Playing')
            except Exception:
                pass
            mgr._execute_playlist_by_id(int(target_playlist_id))
            try:
                logging.info(f"[PaymentPrecheck] Phrase matched; executed playlist id={int(target_playlist_id)}")
            except Exception:
                pass
            try:
                from .xero_util.attach_files_dialog_utils import run_post_playlist_dialog_check as _post_check
                _post_check(app)
            except Exception:
                pass
            return True
        except Exception:
            return False

    return False