import json
import logging
from typing import Any, Dict, List, Optional

import numpy as np
import pandas as pd
import tkinter as tk
from tkinter import messagebox, ttk

log = logging.getLogger(__name__)

MAX_ROWS_TO_DISPLAY_IN_POPUP = 100


def show_results_popup(
    self,
    parent_for_toplevel: tk.Tk,
    df: pd.DataFrame,
    title: str,
    source_df_full: Optional[pd.DataFrame] = None,
    ref_df_full: Optional[pd.DataFrame] = None,
    mappings_list: Optional[List[Dict[str, Any]]] = None,
    source_id_col_name: Optional[str] = None,
    ref_id_col_name: Optional[str] = None,
):
    """
    Displays a new Toplevel window with paginated match results, a details pane,
    and full window controls.
    """
    log.info(
        f"show_results_popup: Entry. Title: '{title}'. DF shape: {df.shape if df is not None else 'None'}"
    )
    log.info(f"DataFrame columns: {list(df.columns) if df is not None else 'None'}")

    win = tk.Toplevel(parent_for_toplevel)
    win.title(title)

    screen_h = win.winfo_screenheight()
    screen_w = win.winfo_screenwidth()
    popup_w = int(screen_w * 0.8)
    popup_h = int(screen_h * 0.85)
    win.geometry(f"{popup_w}x{popup_h}+20+20")
    win.resizable(True, True)

    win.grab_set()

    # --- Local state for the popup window ---
    full_df = df.copy().reset_index(drop=True)
    if "review_status" not in full_df.columns:
        full_df["review_status"] = pd.NA

    current_page_df = pd.DataFrame()

    # --- Main Layout ---
    main_paned_window = ttk.PanedWindow(win, orient=tk.VERTICAL)
    main_paned_window.pack(fill="both", expand=True, padx=10, pady=10)

    # --- Top Pane (List View) ---
    list_view_frame = ttk.Frame(main_paned_window)
    list_view_frame.columnconfigure(0, weight=1)
    list_view_frame.rowconfigure(1, weight=1)
    info_and_nav_frame = ttk.Frame(list_view_frame)
    info_and_nav_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5))
    info_and_nav_frame.columnconfigure(1, weight=1)
    tree_frame = ttk.Frame(list_view_frame)
    tree_frame.grid(row=1, column=0, sticky="nsew")
    tree_frame.rowconfigure(0, weight=1)
    tree_frame.columnconfigure(0, weight=1)

    # Build the list of columns to display in the treeview
    tree_display_cols_list = []
    # Start with essential columns
    for col in ["Domain", "Domain_Append", "confidence_score"]:
        if col in full_df.columns:
            tree_display_cols_list.append(col)

    # Add some ID columns
    for col in full_df.columns:
        if "id" in col.lower() and col not in tree_display_cols_list:
            tree_display_cols_list.append(col)
            if len(tree_display_cols_list) >= 8:
                break

    # Add review status
    if (
        "review_status" in full_df.columns
        and "review_status" not in tree_display_cols_list
    ):
        tree_display_cols_list.append("review_status")

    row_pixel_height = 22
    tree_height_in_rows = max(5, int((popup_h * 0.5) / row_pixel_height))

    popup_tree_widget = ttk.Treeview(
        tree_frame,
        columns=tree_display_cols_list,
        show="headings",
        selectmode="browse",
        height=tree_height_in_rows,
    )
    vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=popup_tree_widget.yview)
    hsb = ttk.Scrollbar(
        tree_frame, orient="horizontal", command=popup_tree_widget.xview
    )
    popup_tree_widget.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
    popup_tree_widget.grid(row=0, column=0, sticky="nsew")
    vsb.grid(row=0, column=1, sticky="ns")
    hsb.grid(row=1, column=0, sticky="ew")

    # --- Review Status Tags ---
    popup_tree_widget.tag_configure("approved", background="#d4edda")
    popup_tree_widget.tag_configure("rejected", background="#f8d7da")
    popup_tree_widget.tag_configure("maybe", background="#fff3cd")

    # --- Bottom Pane (Details View) ---
    detail_frame_container = ttk.Frame(main_paned_window)
    detail_frame_container.columnconfigure(0, weight=1)
    detail_frame_container.rowconfigure(0, weight=1)
    detail_canvas = tk.Canvas(detail_frame_container)
    detail_scrollbar = ttk.Scrollbar(
        detail_frame_container, orient="vertical", command=detail_canvas.yview
    )
    detail_scrollable_frame = ttk.Frame(detail_canvas)
    canvas_frame_id = detail_canvas.create_window(
        (0, 0), window=detail_scrollable_frame, anchor="nw"
    )
    detail_scrollable_frame.bind(
        "<Configure>",
        lambda e: detail_canvas.configure(scrollregion=detail_canvas.bbox("all")),
    )
    detail_canvas.bind(
        "<Configure>",
        lambda e: detail_canvas.itemconfig(canvas_frame_id, width=e.width),
    )
    detail_canvas.configure(yscrollcommand=detail_scrollbar.set)
    detail_canvas.grid(row=0, column=0, sticky="nsew")
    detail_scrollbar.grid(row=0, column=1, sticky="ns")
    detail_scrollable_frame.columnconfigure(0, weight=1)
    score_summary_lf = ttk.LabelFrame(
        detail_scrollable_frame, text="Selected Match Overview", padding="10"
    )
    score_summary_lf.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
    lbl_overall_score_val = ttk.Label(score_summary_lf, text="Score: N/A")
    lbl_overall_score_val.pack(anchor="w")
    score_details_tree = ttk.Treeview(
        score_summary_lf,
        columns=("source_field", "ref_field", "score"),
        show="headings",
        height=4,
    )
    score_details_tree.heading("source_field", text="Source Field")
    score_details_tree.column("source_field", width=200, stretch=True)
    score_details_tree.heading("ref_field", text="Reference Field")
    score_details_tree.column("ref_field", width=200, stretch=True)
    score_details_tree.heading("score", text="Score")
    score_details_tree.column("score", width=80, anchor="e", stretch=False)
    score_details_tree.pack(fill="x", expand=True, pady=5)
    comparison_lf = ttk.LabelFrame(
        detail_scrollable_frame, text="Field Comparison Details", padding="10"
    )
    comparison_lf.grid(row=1, column=0, sticky="ew", padx=5, pady=5)
    comparison_lf.columnconfigure(1, weight=1)
    comparison_lf.columnconfigure(3, weight=1)

    main_paned_window.add(list_view_frame, weight=3)
    main_paned_window.add(detail_frame_container, weight=2)

    # --- Nested Helper Functions ---
    def _make_diff_widget(parent, s1, s2, perspective="source"):
        style = ttk.Style(parent)
        bg_color = style.lookup("TFrame", "background") or "SystemButtonFace"
        text_widget = tk.Text(
            parent,
            height=1,
            wrap=tk.WORD,
            borderwidth=0,
            relief=tk.FLAT,
            background=bg_color,
            font=("TkDefaultFont", 9),
        )
        text_widget.config(state=tk.NORMAL)
        text_widget.delete("1.0", tk.END)

        # Simple diff highlighting
        if s1 != s2:
            text_widget.tag_configure(
                "diff", background="#ffdddd" if perspective == "source" else "#ddffdd"
            )
            text_widget.insert(tk.END, str(s1), "diff")
        else:
            text_widget.insert(tk.END, str(s1))

        text_widget.config(state=tk.DISABLED)
        return text_widget

    def _populate_tree(page_df: pd.DataFrame):
        nonlocal current_page_df
        current_page_df = page_df
        if not popup_tree_widget.winfo_exists():
            return
        popup_tree_widget.delete(*popup_tree_widget.get_children())

        for iid, row in page_df.iterrows():
            values = []
            for col in tree_display_cols_list:
                try:
                    val = row.get(col, "")
                    # Handle different types properly
                    if val is None:
                        val_str = ""
                    elif isinstance(val, (list, np.ndarray)):
                        val_str = str(val)
                    elif isinstance(val, (int, float)):
                        # Check for NaN without using pd.isna on arrays
                        try:
                            if np.isnan(val):
                                val_str = ""
                            elif col == "confidence_score":
                                val_str = f"{val:.2f}"
                            else:
                                val_str = str(val)
                        except:
                            val_str = str(val)
                    else:
                        # For strings and other types
                        val_str = (
                            str(val) if val is not None and str(val) != "nan" else ""
                        )
                except Exception as e:
                    log.warning(f"Error processing value for column {col}: {e}")
                    val_str = ""
                values.append(val_str)

            score = row.get("confidence_score", 0)
            score_tag = "hi" if score >= 90 else "mid" if score >= 80 else "low"

            status = row.get("review_status")
            review_tag = ""
            if status and isinstance(status, str):
                review_tag = status.lower()

            tags_to_apply = [score_tag]
            if review_tag in ["approved", "rejected", "maybe"]:
                tags_to_apply.append(review_tag)

            popup_tree_widget.insert(
                "", "end", iid=str(iid), values=values, tags=tuple(tags_to_apply)
            )

    def _on_match_selected(event=None):
        selection = popup_tree_widget.selection()
        if not selection:
            return
        iid = selection[0]
        try:
            row_data = current_page_df.loc[int(iid)]
        except (KeyError, IndexError, ValueError):
            return

        sc = row_data.get("confidence_score", "N/A")
        lbl_overall_score_val.config(
            text=f"Score: {sc:.2f}" if isinstance(sc, (int, float)) else f"Score: {sc}"
        )
        score_details_tree.delete(*score_details_tree.get_children())

        # Parse field score details
        details_json_str = row_data.get("field_score_details")
        details_list = []
        if isinstance(details_json_str, str):
            try:
                details_list = json.loads(details_json_str)
            except json.JSONDecodeError:
                log.warning(
                    f"Could not parse field_score_details JSON: {details_json_str}"
                )

        if isinstance(details_list, list):
            for d in details_list:
                score_details_tree.insert(
                    "",
                    "end",
                    values=(
                        d.get("source_field"),
                        d.get("ref_field"),
                        f"{d.get('score', 0):.1f}",
                    ),
                )

        # Clear and populate field comparisons
        for w in comparison_lf.winfo_children():
            w.destroy()

        if mappings_list:
            for i, m in enumerate(mappings_list):
                s_field, r_field = m["source"], m["ref"]

                # Find values - handle column name variations
                s_val = ""
                r_val = ""

                # Try different column patterns for source
                s_field_sanitized = s_field.lower().replace(" ", "_").replace("/", "_")
                for col in row_data.index:
                    if col.startswith("s_") and s_field_sanitized in col.lower():
                        s_val = (
                            str(row_data[col])
                            if row_data[col] is not None and str(row_data[col]) != "nan"
                            else ""
                        )
                        break

                # Try different column patterns for reference
                r_field_sanitized = r_field.lower().replace(" ", "_").replace("/", "_")
                for col in row_data.index:
                    if col.startswith("r_") and r_field_sanitized in col.lower():
                        r_val = (
                            str(row_data[col])
                            if row_data[col] is not None and str(row_data[col]) != "nan"
                            else ""
                        )
                        break

                ttk.Label(comparison_lf, text=f"{s_field}:").grid(
                    row=i, column=0, sticky="ne", pady=2, padx=5
                )
                _make_diff_widget(comparison_lf, s_val, r_val, "source").grid(
                    row=i, column=1, sticky="ew"
                )

                ttk.Label(comparison_lf, text=f"{r_field}:").grid(
                    row=i, column=2, sticky="ne", pady=2, padx=5
                )
                _make_diff_widget(comparison_lf, r_val, s_val, "reference").grid(
                    row=i, column=3, sticky="ew"
                )

    def _set_review_status(status: str):
        selection = popup_tree_widget.selection()
        if not selection:
            messagebox.showwarning(
                "No Selection", "Please select a match to review.", parent=win
            )
            return

        selected_iid = selection[0]
        full_df_index = int(selected_iid)

        # Update the main DataFrame and the current page view
        full_df.loc[full_df_index, "review_status"] = status
        if full_df_index in current_page_df.index:
            current_page_df.loc[full_df_index, "review_status"] = status

        # Visually update the row in the treeview
        row_values = list(popup_tree_widget.item(selected_iid, "values"))
        if "review_status" in tree_display_cols_list:
            status_idx = tree_display_cols_list.index("review_status")
            row_values[status_idx] = status
        popup_tree_widget.item(selected_iid, values=row_values)

        # Update tags
        current_tags = list(popup_tree_widget.item(selected_iid, "tags"))
        new_tags = [
            t for t in current_tags if t not in ["approved", "rejected", "maybe"]
        ]
        new_tags.append(status)
        popup_tree_widget.item(selected_iid, tags=tuple(new_tags))

        # Move to the next item
        next_item_iid = popup_tree_widget.next(selected_iid)
        if next_item_iid:
            popup_tree_widget.selection_set(next_item_iid)
            popup_tree_widget.focus(next_item_iid)
            popup_tree_widget.see(next_item_iid)

    # --- Paging & Controls ---
    PAGE_SIZE = MAX_ROWS_TO_DISPLAY_IN_POPUP
    offset = tk.IntVar(value=0)
    nav_buttons_frame = ttk.Frame(info_and_nav_frame)
    nav_buttons_frame.grid(row=0, column=0, sticky="w")
    lbl_page = ttk.Label(info_and_nav_frame, style="Status.TLabel")
    lbl_page.grid(row=0, column=1, sticky="e")

    def _show_page(delta):
        new_offset = offset.get() + delta
        new_offset = max(0, min(new_offset, len(full_df) - 1))
        if len(full_df) - new_offset < PAGE_SIZE:
            new_offset = max(0, len(full_df) - PAGE_SIZE)
        offset.set(new_offset)
        page_df = full_df.iloc[new_offset : new_offset + PAGE_SIZE]
        _populate_tree(page_df)
        lbl_page.config(
            text=f"Displaying {new_offset+1:,} - {min(len(full_df), new_offset+PAGE_SIZE):,} of {len(full_df):,} total results"
        )
        children = popup_tree_widget.get_children()
        if children:
            popup_tree_widget.selection_set(children[0])
            popup_tree_widget.focus(children[0])
            _on_match_selected()

    ttk.Button(
        nav_buttons_frame, text="« Prev", command=lambda: _show_page(-PAGE_SIZE)
    ).pack(side="left")
    ttk.Button(
        nav_buttons_frame, text="Next »", command=lambda: _show_page(PAGE_SIZE)
    ).pack(side="left")

    for col in tree_display_cols_list:
        popup_tree_widget.heading(col, text=col)
        # Set column widths
        if "confidence_score" in col:
            popup_tree_widget.column(col, width=100)
        elif "id" in col.lower():
            popup_tree_widget.column(col, width=150)
        else:
            popup_tree_widget.column(col, width=200)

    popup_tree_widget.bind("<<TreeviewSelect>>", _on_match_selected)

    _show_page(0)

    # --- Review and Close Buttons ---
    review_button_frame = ttk.Frame(win)
    review_button_frame.pack(pady=(5, 10), side="bottom")
    ttk.Button(
        review_button_frame,
        text="Approve (A)",
        command=lambda: _set_review_status("approved"),
    ).pack(side="left", padx=5)
    ttk.Button(
        review_button_frame,
        text="Reject (R)",
        command=lambda: _set_review_status("rejected"),
    ).pack(side="left", padx=5)
    ttk.Button(
        review_button_frame,
        text="Maybe (M)",
        command=lambda: _set_review_status("maybe"),
    ).pack(side="left", padx=5)
    ttk.Separator(review_button_frame, orient="vertical").pack(
        side="left", fill="y", padx=10, pady=2
    )
    close_button = ttk.Button(review_button_frame, text="Close", command=win.destroy)
    close_button.pack(padx=5, side="left")

    # Bind keyboard shortcuts
    win.bind("a", lambda e: _set_review_status("approved"))
    win.bind("A", lambda e: _set_review_status("approved"))
    win.bind("r", lambda e: _set_review_status("rejected"))
    win.bind("R", lambda e: _set_review_status("rejected"))
    win.bind("m", lambda e: _set_review_status("maybe"))
    win.bind("M", lambda e: _set_review_status("maybe"))

    win.wait_window()
    log.info(f"show_results_popup: Exiting for title '{title}'.")
