from __future__ import annotations

import calendar
from datetime import datetime
import hashlib
from pathlib import Path
import tkinter as tk
from tkinter import ttk
from urllib.request import Request

from app_core.http_client import open_url
from urllib.parse import quote, urlparse, urlsplit, urlunsplit

from PIL import Image, ImageTk
from app_core.window_icon import apply_window_icon

from app_core.paths import LOCAL_ROOT


REMOTE_IMAGE_CACHE_DIR = LOCAL_ROOT / "data" / "remote_image_cache"


def is_remote_image_reference(image_path: str | Path | object) -> bool:
    value = str(image_path or "").strip().lower()
    return value.startswith("http://") or value.startswith("https://")


def resolve_image_source(image_path: str | Path | object) -> Path | None:
    raw_value = str(image_path or "").strip()
    if not raw_value:
        return None
    if is_remote_image_reference(raw_value):
        try:
            split = urlsplit(raw_value)
            normalized_url = urlunsplit(
                (
                    split.scheme,
                    split.netloc,
                    quote(split.path, safe="/:@"),
                    quote(split.query, safe="=&?/:@"),
                    quote(split.fragment, safe="=&?/:@"),
                )
            )
            parsed = urlparse(normalized_url)
            suffix = Path(parsed.path).suffix or ".img"
            target_name = f"{hashlib.sha1(raw_value.encode('utf-8')).hexdigest()}{suffix}"
            REMOTE_IMAGE_CACHE_DIR.mkdir(parents=True, exist_ok=True)
            target_path = REMOTE_IMAGE_CACHE_DIR / target_name
            if target_path.exists() and target_path.is_file():
                return target_path
            request = Request(normalized_url, headers={"User-Agent": "CitiFoxERPApp/desktop"})
            with open_url(request, timeout=20) as response:
                target_path.write_bytes(response.read())
            return target_path if target_path.exists() else None
        except Exception:
            return None
    resolved = Path(raw_value)
    if resolved.exists() and resolved.is_file():
        return resolved
    return None


def create_contained_photo(
    image_path: str | Path,
    *,
    target_width: int,
    target_height: int,
    background: str = "#ffffff",
) -> ImageTk.PhotoImage | None:
    resolved = resolve_image_source(image_path)
    if resolved is None:
        return None
    with Image.open(resolved).convert("RGB") as image:
        width, height = image.size
        if width <= 0 or height <= 0:
            return None
        scale = min(target_width / width, target_height / height)
        resized = image.resize(
            (
                max(1, int(width * scale)),
                max(1, int(height * scale)),
            ),
            Image.Resampling.LANCZOS,
        )
        canvas = Image.new("RGB", (target_width, target_height), background)
        canvas.paste(
            resized,
            ((target_width - resized.width) // 2, (target_height - resized.height) // 2),
        )
        return ImageTk.PhotoImage(canvas)


def configure_striped_treeview(tree: ttk.Treeview) -> None:
    theme = getattr(tree.winfo_toplevel(), "_desktop_theme", {}) if tree.winfo_exists() else {}
    odd_row = str(theme.get("tree_odd", "#f7f9fc")) if isinstance(theme, dict) else "#f7f9fc"
    even_row = str(theme.get("tree_even", "#ffffff")) if isinstance(theme, dict) else "#ffffff"
    tree.tag_configure("odd_row", background=odd_row)
    tree.tag_configure("even_row", background=even_row)


def bind_date_picker_helpers(app_cls: type) -> None:
    def bind_date_entry_picker(self, entry: ttk.Entry, variable: tk.StringVar) -> None:
        entry.configure(state="readonly")
        entry.bind("<Button-1>", lambda _event, widget=entry, var=variable: self.open_date_picker(widget, var))

    def open_date_picker(self, anchor_widget: tk.Widget, target_var: tk.StringVar) -> str:
        if self.active_date_picker is not None and self.active_date_picker.winfo_exists():
            try:
                self.active_date_picker.destroy()
            except Exception:
                pass
            self.active_date_picker = None

        initial_value = str(target_var.get() or "").strip()
        initial_date = self.parse_date_filter_value(initial_value) or datetime.now()
        selected_date = self.parse_date_filter_value(initial_value)
        owner = anchor_widget.winfo_toplevel()
        owner_had_grab = False
        try:
            owner_had_grab = owner.grab_current() == owner
        except Exception:
            owner_had_grab = False
        if owner_had_grab:
            try:
                owner.grab_release()
            except Exception:
                pass
        picker = tk.Toplevel(owner)
        picker.title("Select date")
        picker.transient(owner)
        picker.resizable(False, False)
        apply_window_icon(picker, default=False)
        picker.attributes("-topmost", True)
        picker.protocol("WM_DELETE_WINDOW", lambda: self.close_date_picker(picker))
        picker._date_picker_owner = owner  # type: ignore[attr-defined]
        picker._date_picker_restore_grab = owner_had_grab  # type: ignore[attr-defined]
        self.active_date_picker = picker
        try:
            picker.grab_set()
        except Exception:
            pass

        state = {"year": initial_date.year, "month": initial_date.month}
        container = ttk.Frame(picker, padding=10)
        container.pack(fill="both", expand=True)

        header = ttk.Frame(container)
        header.pack(fill="x")
        ttk.Button(header, text="<", width=3, command=lambda: shift_month(-1)).pack(side="left")
        title_var = tk.StringVar()
        ttk.Label(header, textvariable=title_var, anchor="center", font=("Segoe UI", 10, "bold")).pack(side="left", expand=True)
        ttk.Button(header, text=">", width=3, command=lambda: shift_month(1)).pack(side="right")

        calendar_frame = ttk.Frame(container)
        calendar_frame.pack(fill="both", expand=True, pady=(8, 8))

        actions = ttk.Frame(container)
        actions.pack(fill="x")
        ttk.Button(actions, text="Clear", command=lambda: select_date(None)).pack(side="left")
        ttk.Button(actions, text=self.t("close"), command=lambda: self.close_date_picker(picker)).pack(side="right")

        def select_date(day: int | None) -> None:
            if day is None:
                target_var.set("")
            else:
                target_var.set(f"{state['year']:04d}-{state['month']:02d}-{day:02d}")
            self.close_date_picker(picker)

        def shift_month(offset: int) -> None:
            month = state["month"] + offset
            year = state["year"]
            if month < 1:
                month = 12
                year -= 1
            elif month > 12:
                month = 1
                year += 1
            state["year"] = year
            state["month"] = month
            render_calendar()

        def render_calendar() -> None:
            today = datetime.now()
            for child in calendar_frame.winfo_children():
                child.destroy()
            title_var.set(f"{calendar.month_name[state['month']]} {state['year']}")
            for col_index, label in enumerate(("Mo", "Tu", "We", "Th", "Fr", "Sa", "Su")):
                ttk.Label(calendar_frame, text=label, anchor="center", width=4).grid(row=0, column=col_index, padx=1, pady=1)
            for row_index, week in enumerate(calendar.monthcalendar(state["year"], state["month"]), start=1):
                for col_index, day in enumerate(week):
                    if day == 0:
                        ttk.Label(calendar_frame, text="", width=4).grid(row=row_index, column=col_index, padx=1, pady=1)
                        continue
                    is_today = state["year"] == today.year and state["month"] == today.month and day == today.day
                    is_selected = (
                        selected_date is not None
                        and state["year"] == selected_date.year
                        and state["month"] == selected_date.month
                        and day == selected_date.day
                    )
                    background = "#f4f4f4"
                    foreground = "#202020"
                    active_background = "#e7edf5"
                    relief = "raised"
                    border_width = 1
                    if is_today and is_selected:
                        background = "#d7ead7"
                        active_background = "#c6dfc6"
                        foreground = "#1f5f1f"
                        relief = "solid"
                        border_width = 2
                    elif is_selected:
                        background = "#dce9f8"
                        active_background = "#c9dcf3"
                        foreground = "#1c4670"
                        relief = "solid"
                        border_width = 2
                    elif is_today:
                        background = "#fff1b8"
                        active_background = "#ffe58f"
                        foreground = "#8a5a00"
                        relief = "solid"
                        border_width = 2
                    tk.Button(
                        calendar_frame,
                        text=str(day),
                        width=4,
                        relief=relief,
                        borderwidth=border_width,
                        bg=background,
                        fg=foreground,
                        activebackground=active_background,
                        activeforeground=foreground,
                        command=lambda selected_day=day: select_date(selected_day),
                    ).grid(row=row_index, column=col_index, padx=1, pady=1)

        picker.update_idletasks()
        x = anchor_widget.winfo_rootx()
        y = anchor_widget.winfo_rooty() + anchor_widget.winfo_height() + 2
        picker.geometry(f"+{x}+{y}")
        render_calendar()
        picker.bind("<Escape>", lambda _event: self.close_date_picker(picker))
        return "break"

    def close_date_picker(self, picker: tk.Toplevel) -> None:
        owner = getattr(picker, "_date_picker_owner", None)
        restore_grab = bool(getattr(picker, "_date_picker_restore_grab", False))
        try:
            picker.destroy()
        except Exception:
            pass
        if restore_grab and owner is not None:
            try:
                if owner.winfo_exists():
                    owner.grab_set()
            except Exception:
                pass
        if self.active_date_picker is picker:
            self.active_date_picker = None

    app_cls.bind_date_entry_picker = bind_date_entry_picker
    app_cls.open_date_picker = open_date_picker
    app_cls.close_date_picker = close_date_picker
