#!/usr/bin/env python3
"""
Results Popup Window for Fuzzy Matcher GUI
==========================================
Displays match results with sorting, pagination, and review functionality.
"""

import tkinter as tk
from tkinter import ttk, messagebox
import pandas as pd
from typing import Dict, List, Optional, Any
import json
import ast
import logging

log = logging.getLogger(__name__)

# Constants
ROWS_PER_PAGE = 100
DEFAULT_PAGE_SIZES = [50, 100, 200, 500, 1000]


class ResultsPopup(tk.Toplevel):
    """Popup window for displaying and interacting with match results."""

    def __init__(
        self,
        parent: tk.Widget,
        df: pd.DataFrame,
        title: str = "Match Results",
        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,
    ):
        """
        Initialize the results popup window.

        Args:
            parent: Parent window
            df: Results dataframe to display
            title: Window title
            source_df_full: Full source dataframe for detailed comparison
            ref_df_full: Full reference dataframe for detailed comparison
            mappings_list: List of field mappings
            source_id_col_name: Name of source ID column
            ref_id_col_name: Name of reference ID column
        """
        super().__init__(parent)

        # Store parameters
        self.parent = parent
        self.original_df = df.copy()
        self.df = df.copy()
        self.source_df_full = source_df_full
        self.ref_df_full = ref_df_full
        self.mappings_list = mappings_list or []
        self.source_id_col_name = source_id_col_name
        self.ref_id_col_name = ref_id_col_name

        # Add review_status column if not present
        if "review_status" not in self.df.columns:
            self.df["review_status"] = ""
            self.original_df["review_status"] = ""

        # Window setup
        self.title(title)
        self.geometry("1400x800")
        self.minsize(1200, 600)

        # State variables
        self.current_page = 0
        self.rows_per_page = ROWS_PER_PAGE
        self.sort_column = None
        self.sort_reverse = False
        self.selected_row_data = None

        # UI References
        self.tree = None
        self.overview_text = None
        self.comparison_notebook = None
        self.field_comparison_frames = {}

        # Build UI
        self._build_ui()

        # Load initial data
        self._refresh_tree()

        # Center window
        self._center_window()

        # Focus
        self.focus()

    def _build_ui(self):
        """Build the user interface."""
        # Main container
        main_frame = ttk.Frame(self)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Create paned window for resizable sections
        paned = ttk.PanedWindow(main_frame, orient=tk.VERTICAL)
        paned.pack(fill=tk.BOTH, expand=True)

        # Top section - Results table with controls
        top_frame = ttk.Frame(paned)
        paned.add(top_frame, weight=3)

        # Controls frame
        controls_frame = ttk.Frame(top_frame)
        controls_frame.pack(fill=tk.X, pady=(0, 5))

        # Left controls - Pagination
        left_controls = ttk.Frame(controls_frame)
        left_controls.pack(side=tk.LEFT, fill=tk.X, expand=True)

        # Page navigation
        ttk.Label(left_controls, text="Page:").pack(side=tk.LEFT, padx=(0, 5))

        self.page_var = tk.StringVar()
        self.page_dropdown = ttk.Combobox(
            left_controls, textvariable=self.page_var, width=10, state="readonly"
        )
        self.page_dropdown.pack(side=tk.LEFT, padx=(0, 10))
        self.page_dropdown.bind("<<ComboboxSelected>>", self._on_page_changed)

        # Page size
        ttk.Label(left_controls, text="Rows per page:").pack(side=tk.LEFT, padx=(10, 5))

        self.page_size_var = tk.IntVar(value=self.rows_per_page)
        self.page_size_dropdown = ttk.Combobox(
            left_controls,
            textvariable=self.page_size_var,
            values=DEFAULT_PAGE_SIZES,
            width=8,
            state="readonly",
        )
        self.page_size_dropdown.pack(side=tk.LEFT, padx=(0, 10))
        self.page_size_dropdown.bind("<<ComboboxSelected>>", self._on_page_size_changed)

        # Results count
        self.count_label = ttk.Label(left_controls, text="")
        self.count_label.pack(side=tk.LEFT, padx=(20, 0))

        # Right controls - Actions
        right_controls = ttk.Frame(controls_frame)
        right_controls.pack(side=tk.RIGHT)

        # Review buttons
        self.btn_approve = ttk.Button(
            right_controls,
            text="✓ Approve",
            command=lambda: self._set_review_status("approved"),
        )
        self.btn_approve.pack(side=tk.LEFT, padx=2)

        self.btn_reject = ttk.Button(
            right_controls,
            text="✗ Reject",
            command=lambda: self._set_review_status("rejected"),
        )
        self.btn_reject.pack(side=tk.LEFT, padx=2)

        self.btn_clear_review = ttk.Button(
            right_controls,
            text="Clear Review",
            command=lambda: self._set_review_status(""),
        )
        self.btn_clear_review.pack(side=tk.LEFT, padx=(2, 10))

        # Export button
        self.btn_export = ttk.Button(
            right_controls,
            text="Export with Reviews",
            command=self._export_with_reviews,
        )
        self.btn_export.pack(side=tk.LEFT)

        # Results tree
        tree_frame = ttk.Frame(top_frame)
        tree_frame.pack(fill=tk.BOTH, expand=True)

        # Create tree with scrollbars
        tree_scroll_y = ttk.Scrollbar(tree_frame)
        tree_scroll_y.pack(side=tk.RIGHT, fill=tk.Y)

        tree_scroll_x = ttk.Scrollbar(tree_frame, orient=tk.HORIZONTAL)
        tree_scroll_x.pack(side=tk.BOTTOM, fill=tk.X)

        # Determine columns
        columns = list(self.df.columns)

        self.tree = ttk.Treeview(
            tree_frame,
            columns=columns,
            show="tree headings",
            yscrollcommand=tree_scroll_y.set,
            xscrollcommand=tree_scroll_x.set,
        )
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        tree_scroll_y.config(command=self.tree.yview)
        tree_scroll_x.config(command=self.tree.xview)

        # Configure columns
        self.tree.column("#0", width=50, stretch=False)  # Row number
        self.tree.heading("#0", text="#")

        for col in columns:
            # Set column widths based on content
            if col == "review_status":
                width = 100
            elif any(x in col.lower() for x in ["score", "confidence"]):
                width = 80
            elif any(x in col.lower() for x in ["id", "key"]):
                width = 120
            else:
                width = 150

            self.tree.column(col, width=width, anchor=tk.W)
            self.tree.heading(
                col, text=col, command=lambda c=col: self._sort_by_column(c)
            )

        # Bind selection event
        self.tree.bind("<<TreeviewSelect>>", self._on_selection_changed)
        self.tree.bind("<Double-Button-1>", self._on_double_click)

        # Bottom section - Details view
        bottom_frame = ttk.Frame(paned)
        paned.add(bottom_frame, weight=2)

        # Create notebook for organized details
        details_notebook = ttk.Notebook(bottom_frame)
        details_notebook.pack(fill=tk.BOTH, expand=True, pady=(5, 0))

        # Overview tab
        overview_frame = ttk.Frame(details_notebook)
        details_notebook.add(overview_frame, text="Match Overview")

        self.overview_text = tk.Text(overview_frame, wrap=tk.WORD, height=8, width=80)
        self.overview_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        overview_scroll = ttk.Scrollbar(
            overview_frame, command=self.overview_text.yview
        )
        overview_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        self.overview_text.config(yscrollcommand=overview_scroll.set)

        # Field comparison tab
        comparison_frame = ttk.Frame(details_notebook)
        details_notebook.add(comparison_frame, text="Field Comparison")

        # Create scrollable frame for field comparisons
        comparison_canvas = tk.Canvas(comparison_frame)
        comparison_scroll = ttk.Scrollbar(
            comparison_frame, orient="vertical", command=comparison_canvas.yview
        )
        self.comparison_inner_frame = ttk.Frame(comparison_canvas)

        comparison_canvas.configure(yscrollcommand=comparison_scroll.set)
        canvas_frame = comparison_canvas.create_window(
            (0, 0), window=self.comparison_inner_frame, anchor="nw"
        )

        comparison_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        comparison_scroll.pack(side=tk.RIGHT, fill=tk.Y)

        # Update scroll region when frame changes
        def configure_scroll(event):
            comparison_canvas.configure(scrollregion=comparison_canvas.bbox("all"))
            # Make canvas width match frame
            canvas_width = event.width
            comparison_canvas.itemconfig(canvas_frame, width=canvas_width)

        self.comparison_inner_frame.bind("<Configure>", configure_scroll)
        comparison_canvas.bind(
            "<Configure>",
            lambda e: comparison_canvas.itemconfig(canvas_frame, width=e.width),
        )

        # Enable mousewheel scrolling
        def _on_mousewheel(event):
            comparison_canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")

        comparison_canvas.bind_all("<MouseWheel>", _on_mousewheel)

        self.comparison_notebook = details_notebook

    def _refresh_tree(self):
        """Refresh the tree view with current data."""
        # Clear existing items
        for item in self.tree.get_children():
            self.tree.delete(item)

        # Calculate pagination
        total_rows = len(self.df)
        total_pages = (total_rows + self.rows_per_page - 1) // self.rows_per_page

        # Update page dropdown
        if total_pages > 0:
            page_options = [f"{i+1} of {total_pages}" for i in range(total_pages)]
            self.page_dropdown["values"] = page_options

            # Ensure current page is valid
            if self.current_page >= total_pages:
                self.current_page = total_pages - 1
            if self.current_page < 0:
                self.current_page = 0

            self.page_var.set(page_options[self.current_page])
        else:
            self.page_dropdown["values"] = []
            self.page_var.set("")

        # Get page data
        start_idx = self.current_page * self.rows_per_page
        end_idx = min(start_idx + self.rows_per_page, total_rows)
        page_df = self.df.iloc[start_idx:end_idx]

        # Update count label
        if total_rows > 0:
            self.count_label.config(
                text=f"Showing {start_idx+1}-{end_idx} of {total_rows} results"
            )
        else:
            self.count_label.config(text="No results")

        # Insert rows
        for idx, (row_idx, row) in enumerate(page_df.iterrows()):
            # Determine row tag based on review status
            tags = []
            if "review_status" in row:
                if row["review_status"] == "approved":
                    tags.append("approved")
                elif row["review_status"] == "rejected":
                    tags.append("rejected")

            # Format values
            values = []
            for col in self.df.columns:
                val = row[col]
                # Handle array/list values or scalar NaN checks
                try:
                    is_na = pd.isna(val)
                    # If val is an array/list, pd.isna returns an array
                    if hasattr(is_na, "__iter__") and not isinstance(
                        is_na, (bool, str)
                    ):
                        # For arrays/lists, check if all values are NaN
                        is_na = all(pd.isna(v) for v in val)
                except (TypeError, ValueError):
                    is_na = False

                if is_na:
                    values.append("")
                elif isinstance(val, float):
                    # Format float values
                    if col.lower().endswith("score") or "confidence" in col.lower():
                        values.append(f"{val:.1f}")
                    else:
                        values.append(f"{val:.2f}")
                else:
                    values.append(str(val))

            # Insert item
            item_id = self.tree.insert(
                "", "end", text=str(start_idx + idx + 1), values=values, tags=tags
            )

        # Configure tag colors
        self.tree.tag_configure("approved", background="#d4edda")
        self.tree.tag_configure("rejected", background="#f8d7da")

    def _sort_by_column(self, column: str):
        """Sort the dataframe by the specified column."""
        if self.sort_column == column:
            # Toggle sort direction
            self.sort_reverse = not self.sort_reverse
        else:
            # New column, sort ascending
            self.sort_column = column
            self.sort_reverse = False

        # Sort dataframe
        self.df = self.df.sort_values(by=column, ascending=not self.sort_reverse)

        # Reset to first page and refresh
        self.current_page = 0
        self._refresh_tree()

    def _on_page_changed(self, event=None):
        """Handle page selection change."""
        selection = self.page_var.get()
        if selection:
            # Extract page number
            page_num = int(selection.split()[0]) - 1
            if page_num != self.current_page:
                self.current_page = page_num
                self._refresh_tree()

    def _on_page_size_changed(self, event=None):
        """Handle page size change."""
        new_size = self.page_size_var.get()
        if new_size != self.rows_per_page and new_size > 0:
            # Calculate which row we're currently looking at
            current_first_row = self.current_page * self.rows_per_page

            # Update page size
            self.rows_per_page = new_size

            # Calculate new page to maintain position
            self.current_page = current_first_row // self.rows_per_page

            # Refresh
            self._refresh_tree()

    def _on_selection_changed(self, event=None):
        """Handle tree selection change."""
        selection = self.tree.selection()
        if not selection:
            self._clear_details()
            return

        # Get selected item data
        item = self.tree.item(selection[0])
        row_num = int(item["text"]) - 1  # Convert display number to index

        # Get actual dataframe row
        if 0 <= row_num < len(self.df):
            self.selected_row_data = self.df.iloc[row_num]
            self._update_details()

    def _on_double_click(self, event=None):
        """Handle double-click on tree item."""
        # Could open a detailed view window here
        pass

    def _update_details(self):
        """Update the details panels with selected row information."""
        if self.selected_row_data is None:
            return

        # Update overview
        self._update_overview()

        # Update field comparisons
        self._update_field_comparisons()

    def _update_overview(self):
        """Update the match overview text."""
        self.overview_text.config(state=tk.NORMAL)
        self.overview_text.delete(1.0, tk.END)

        # Build overview text
        overview_lines = []

        # Match score
        if "confidence_score" in self.selected_row_data:
            score = self.selected_row_data["confidence_score"]
            overview_lines.append(f"Match Confidence: {score:.1f}%")
            overview_lines.append("")

        # Source and reference IDs
        if (
            self.source_id_col_name
            and self.source_id_col_name in self.selected_row_data
        ):
            overview_lines.append(
                f"Source ID: {self.selected_row_data[self.source_id_col_name]}"
            )

        if self.ref_id_col_name and self.ref_id_col_name in self.selected_row_data:
            overview_lines.append(
                f"Reference ID: {self.selected_row_data[self.ref_id_col_name]}"
            )

        if overview_lines and overview_lines[-1] != "":
            overview_lines.append("")

        # Field scores breakdown
        if "field_scores" in self.selected_row_data:
            field_scores_str = self.selected_row_data["field_scores"]
            if field_scores_str and not pd.isna(field_scores_str):
                try:
                    # Parse field scores
                    if isinstance(field_scores_str, str):
                        field_scores = (
                            json.loads(field_scores_str)
                            if field_scores_str.startswith("[")
                            else ast.literal_eval(field_scores_str)
                        )
                    else:
                        field_scores = field_scores_str

                    if isinstance(field_scores, list):
                        overview_lines.append("Field Scores:")
                        for field_score in field_scores:
                            if isinstance(field_score, dict):
                                field = field_score.get("source_field", "Unknown")
                                score = field_score.get("score", 0)
                                weight = field_score.get("weight", 1)
                                overview_lines.append(
                                    f"  • {field}: {score:.1f} (weight: {weight})"
                                )
                except Exception as e:
                    log.debug(f"Could not parse field scores: {e}")

        # Review status
        if "review_status" in self.selected_row_data:
            status = self.selected_row_data["review_status"]
            if status:
                overview_lines.append("")
                overview_lines.append(f"Review Status: {status.title()}")

        # Insert text
        self.overview_text.insert(1.0, "\n".join(overview_lines))
        self.overview_text.config(state=tk.DISABLED)

    def _update_field_comparisons(self):
        """Update the field comparison details."""
        # Clear existing comparisons
        for widget in self.comparison_inner_frame.winfo_children():
            widget.destroy()

        self.field_comparison_frames = {}

        # Get source and reference data if available
        source_data = None
        ref_data = None

        if self.source_df_full is not None and self.source_id_col_name:
            source_id = self.selected_row_data.get(self.source_id_col_name)
            if source_id is not None:
                mask = self.source_df_full[self.source_id_col_name] == source_id
                if mask.any():
                    source_data = self.source_df_full[mask].iloc[0]

        if self.ref_df_full is not None and self.ref_id_col_name:
            ref_id = self.selected_row_data.get(self.ref_id_col_name)
            if ref_id is not None:
                mask = self.ref_df_full[self.ref_id_col_name] == ref_id
                if mask.any():
                    ref_data = self.ref_df_full[mask].iloc[0]

        # Create comparison for each mapped field
        for i, mapping in enumerate(self.mappings_list):
            source_field = mapping.get("source")
            ref_field = mapping.get("ref")

            if not source_field:
                continue

            # Create frame for this field
            field_frame = ttk.LabelFrame(
                self.comparison_inner_frame,
                text=f"{source_field} <-> {ref_field or source_field}",
                padding=10,
            )
            field_frame.pack(fill=tk.X, padx=5, pady=5)

            # Create text widgets for comparison
            text_frame = ttk.Frame(field_frame)
            text_frame.pack(fill=tk.BOTH, expand=True)

            # Source value
            source_label = ttk.Label(
                text_frame, text="Source:", font=("TkDefaultFont", 9, "bold")
            )
            source_label.grid(row=0, column=0, sticky=tk.W, padx=(0, 10))

            source_text = tk.Text(text_frame, height=3, width=50, wrap=tk.WORD)
            source_text.grid(row=0, column=1, sticky=tk.EW, padx=(0, 10))

            # Reference value
            ref_label = ttk.Label(
                text_frame, text="Reference:", font=("TkDefaultFont", 9, "bold")
            )
            ref_label.grid(row=1, column=0, sticky=tk.W, padx=(0, 10), pady=(5, 0))

            ref_text = tk.Text(text_frame, height=3, width=50, wrap=tk.WORD)
            ref_text.grid(row=1, column=1, sticky=tk.EW, padx=(0, 10), pady=(5, 0))

            text_frame.columnconfigure(1, weight=1)

            # Get values
            source_val = ""
            ref_val = ""

            if source_data is not None and source_field in source_data:
                source_val = (
                    str(source_data[source_field])
                    if not pd.isna(source_data[source_field])
                    else ""
                )

            if ref_data is not None and ref_field and ref_field in ref_data:
                ref_val = (
                    str(ref_data[ref_field]) if not pd.isna(ref_data[ref_field]) else ""
                )

            # Apply diff highlighting
            self._apply_diff_highlighting(source_text, ref_text, source_val, ref_val)

            # Make read-only
            source_text.config(state=tk.DISABLED)
            ref_text.config(state=tk.DISABLED)

            # Store references
            self.field_comparison_frames[source_field] = {
                "frame": field_frame,
                "source_text": source_text,
                "ref_text": ref_text,
            }

    def _apply_diff_highlighting(
        self, source_text: tk.Text, ref_text: tk.Text, source_val: str, ref_val: str
    ):
        """Apply diff highlighting to text widgets."""
        # Configure tags
        source_text.tag_configure("added", background="#d4edda", foreground="#155724")
        source_text.tag_configure("removed", background="#f8d7da", foreground="#721c24")
        ref_text.tag_configure("added", background="#d4edda", foreground="#155724")
        ref_text.tag_configure("removed", background="#f8d7da", foreground="#721c24")

        # Insert values
        source_text.insert(1.0, source_val)
        ref_text.insert(1.0, ref_val)

        # Apply highlighting if values differ
        if source_val != ref_val:
            # Simple character-based diff
            import difflib

            matcher = difflib.SequenceMatcher(None, source_val, ref_val)

            # Highlight differences in source text
            for tag, i1, i2, j1, j2 in matcher.get_opcodes():
                if tag == "replace" or tag == "delete":
                    source_text.tag_add("removed", f"1.0+{i1}c", f"1.0+{i2}c")

            # Highlight differences in ref text
            for tag, i1, i2, j1, j2 in matcher.get_opcodes():
                if tag == "replace" or tag == "insert":
                    ref_text.tag_add("added", f"1.0+{j1}c", f"1.0+{j2}c")

    def _set_review_status(self, status: str):
        """Set review status for selected rows."""
        selection = self.tree.selection()
        if not selection:
            messagebox.showinfo("No Selection", "Please select a row to review")
            return

        # Update status in dataframe
        for item in selection:
            row_num = int(self.tree.item(item)["text"]) - 1
            if 0 <= row_num < len(self.df):
                self.df.iloc[row_num, self.df.columns.get_loc("review_status")] = status
                self.original_df.iloc[
                    row_num, self.original_df.columns.get_loc("review_status")
                ] = status

        # Refresh tree to show updated colors
        self._refresh_tree()

        # Re-select items
        for item in selection:
            self.tree.selection_add(item)

    def _export_with_reviews(self):
        """Export results with review status."""
        from tkinter import filedialog

        filename = filedialog.asksaveasfilename(
            defaultextension=".csv",
            filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
            title="Export Results with Review Status",
        )

        if filename:
            try:
                # Use original dataframe order but with updated review status
                self.original_df.to_csv(filename, index=False)
                messagebox.showinfo(
                    "Export Complete", f"Results exported to:\n{filename}"
                )
            except Exception as e:
                messagebox.showerror("Export Error", f"Failed to export: {str(e)}")

    def _clear_details(self):
        """Clear detail views."""
        self.overview_text.config(state=tk.NORMAL)
        self.overview_text.delete(1.0, tk.END)
        self.overview_text.config(state=tk.DISABLED)

        # Clear field comparisons
        for widget in self.comparison_inner_frame.winfo_children():
            widget.destroy()

    def _center_window(self):
        """Center the window on screen."""
        self.update()
        x = (self.winfo_screenwidth() - self.winfo_width()) // 2
        y = (self.winfo_screenheight() - self.winfo_height()) // 2
        self.geometry(f"+{x}+{y}")
