from __future__ import annotations

import socket
import subprocess
import sys
import tkinter as tk
from datetime import datetime, timedelta, timezone
from pathlib import Path
from tkinter import messagebox, ttk

from app_core.auth import load_auth_settings, save_auth_settings
from app_core.mail_service import normalize_mail_settings, send_email
from app_core.paths import AUTH_SETTINGS_PATH, DB_PATH, MAIL_OUTBOX_DIR, RUN_LOCAL_SERVER_SCRIPT, SERVER_SETTINGS_PATH
from app_core.server_api import (
    authenticate_user as authenticate_user_via_api,
    create_password_reset_token as create_password_reset_token_via_api,
    fetch_user_by_email,
    get_password_reset_token as get_password_reset_token_via_api,
    register_user as register_user_via_api,
    update_password_with_token as update_password_with_token_via_api,
)
from app_core.server_settings import DEFAULT_SERVER_SETTINGS, load_server_settings
from app_core.version import format_versioned_app_title
from database import get_app_setting


def load_runtime_server_settings() -> dict:
    return load_server_settings(SERVER_SETTINGS_PATH)


def ensure_local_server_running_for_settings(settings: dict) -> None:
    data_mode = str(settings.get("data_mode", "local")).strip().lower() or "local"
    if data_mode != "local" or sys.platform != "win32":
        return
    try:
        with socket.create_connection(("127.0.0.1", 8000), timeout=0.5):
            return
    except OSError:
        pass
    if not RUN_LOCAL_SERVER_SCRIPT.exists():
        return
    try:
        creationflags = getattr(subprocess, "CREATE_NO_WINDOW", 0)
        startupinfo = None
        if hasattr(subprocess, "STARTUPINFO"):
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = 0
        subprocess.Popen(
            ["cmd", "/c", str(RUN_LOCAL_SERVER_SCRIPT)],
            cwd=str(RUN_LOCAL_SERVER_SCRIPT.parent),
            creationflags=creationflags,
            startupinfo=startupinfo,
        )
    except Exception:
        pass


class LoginDialog:
    def __init__(
        self,
        root: tk.Tk,
        texts_by_language: dict[str, dict[str, str]],
        language_code: str = "en",
        reset_token: str = "",
        *,
        apply_window_icon_fn,
        validate_email_fn,
    ) -> None:
        self.root = root
        self.texts_by_language = texts_by_language
        self.language_code = language_code if language_code in texts_by_language else "en"
        self.apply_window_icon_fn = apply_window_icon_fn
        self.validate_email_fn = validate_email_fn
        self.auth_settings = load_auth_settings(AUTH_SETTINGS_PATH)
        self.server_settings = load_runtime_server_settings()
        ensure_local_server_running_for_settings(self.server_settings)
        self.result: dict | None = None
        self.pending_reset_token = str(reset_token or "").strip()

        defaults = self.texts_by_language.get("en", {})
        active = self.texts_by_language.get(self.language_code, defaults)
        self._texts = {**defaults, **active}

        self.login_history = [
            str(email).strip()
            for email in self.auth_settings.get("login_history", [])
            if str(email).strip()
        ]
        self.remembered_accounts = {
            str(email).strip(): str(password)
            for email, password in dict(self.auth_settings.get("remembered_accounts", {})).items()
            if str(email).strip()
        }
        initial_email = str(self.auth_settings.get("email", "")).strip()
        initial_password = str(self.remembered_accounts.get(initial_email, ""))
        self.email_var = tk.StringVar(value=initial_email)
        self.password_var = tk.StringVar(value=initial_password)
        self.remember_password_var = tk.BooleanVar(value=bool(initial_email and initial_email in self.remembered_accounts))
        self.show_password_var = tk.BooleanVar(value=False)
        self.status_var = tk.StringVar(value=self.t("login_welcome"))

        self.window = root
        self.window.title(format_versioned_app_title(self.t("login_title")))
        self.window.geometry("520x420")
        self.window.minsize(520, 420)
        self.window.resizable(False, False)
        self.window.protocol("WM_DELETE_WINDOW", self.on_close)
        self.apply_window_icon_fn(self.window)

        self._build_ui()
        self.window.update_idletasks()
        self.center_window()
        if self.pending_reset_token:
            self.window.after(150, self.open_pending_reset_dialog)

    def t(self, key: str) -> str:
        return str(self._texts.get(key, key))

    def _build_ui(self) -> None:
        container = ttk.Frame(self.window, padding=22)
        container.pack(fill="both", expand=True)
        container.columnconfigure(0, weight=1)

        title = ttk.Label(container, text=self.t("login_title"), font=("Segoe UI", 18, "bold"))
        title.grid(row=0, column=0, sticky="w")

        subtitle = ttk.Label(container, text=self.t("login_welcome"), wraplength=390, justify="left")
        subtitle.grid(row=1, column=0, sticky="w", pady=(6, 18))

        ttk.Label(container, text=self.t("login_email")).grid(row=2, column=0, sticky="w")
        email_entry = ttk.Combobox(container, textvariable=self.email_var, values=self.login_history)
        email_entry.grid(row=3, column=0, sticky="ew", pady=(6, 12))
        email_entry.bind("<<ComboboxSelected>>", self.on_email_selected)

        ttk.Label(container, text=self.t("login_password")).grid(row=4, column=0, sticky="w")
        password_row = ttk.Frame(container)
        password_row.grid(row=5, column=0, sticky="ew", pady=(6, 8))
        password_row.columnconfigure(0, weight=1)

        self.password_entry = ttk.Entry(password_row, textvariable=self.password_var, show="*")
        self.password_entry.grid(row=0, column=0, sticky="ew")

        show_box = ttk.Checkbutton(
            password_row,
            text=self.t("login_show_password"),
            variable=self.show_password_var,
            command=self.toggle_password_visibility,
        )
        show_box.grid(row=0, column=1, sticky="w", padx=(8, 0))

        remember_box = ttk.Checkbutton(
            container,
            text=self.t("login_remember_password"),
            variable=self.remember_password_var,
        )
        remember_box.grid(row=6, column=0, sticky="w", pady=(0, 10))

        status_label = ttk.Label(container, textvariable=self.status_var, foreground="#8a4b1f", wraplength=390)
        status_label.grid(row=7, column=0, sticky="w", pady=(0, 12))

        login_button = ttk.Button(container, text=self.t("login_button"), command=self.try_login)
        login_button.grid(row=8, column=0, sticky="ew")

        action_row = ttk.Frame(container)
        action_row.grid(row=9, column=0, sticky="ew", pady=(12, 0))
        action_row.columnconfigure(0, weight=1)
        action_row.columnconfigure(1, weight=1)

        ttk.Button(
            action_row,
            text=self.t("login_forgot_password"),
            command=self.open_forgot_password_dialog,
        ).grid(row=0, column=0, sticky="ew", padx=(0, 6))
        ttk.Button(
            action_row,
            text=self.t("login_register"),
            command=self.open_register_dialog,
        ).grid(row=0, column=1, sticky="ew", padx=(6, 0))

        email_entry.focus_set()
        self.window.bind("<Return>", lambda _event: self.try_login())

    def center_window(self) -> None:
        self.window.update_idletasks()
        width = max(520, self.window.winfo_reqwidth())
        height = max(420, self.window.winfo_reqheight())
        screen_width = self.window.winfo_screenwidth()
        screen_height = self.window.winfo_screenheight()
        x = max(0, int((screen_width - width) / 2))
        y = max(0, int((screen_height - height) / 3))
        self.window.geometry(f"{width}x{height}+{x}+{y}")

    def center_dialog(self, dialog: tk.Toplevel, *, min_width: int, min_height: int, padding: int = 20) -> None:
        dialog.update_idletasks()
        width = max(min_width, dialog.winfo_reqwidth() + padding)
        height = max(min_height, dialog.winfo_reqheight() + padding)
        screen_width = dialog.winfo_screenwidth()
        screen_height = dialog.winfo_screenheight()
        x = max(0, int((screen_width - width) / 2))
        y = max(0, int((screen_height - height) / 3))
        dialog.geometry(f"{width}x{height}+{x}+{y}")

    def open_pending_reset_dialog(self) -> None:
        token = self.pending_reset_token
        self.pending_reset_token = ""
        if token:
            self.open_reset_token_dialog(token)

    def toggle_password_visibility(self) -> None:
        self.password_entry.configure(show="" if self.show_password_var.get() else "*")

    def load_server_settings_from_db(self) -> dict:
        return load_server_settings(SERVER_SETTINGS_PATH)

    def load_mail_settings_from_db(self) -> dict:
        payload = get_app_setting(DB_PATH, "mail_settings", {})
        settings = normalize_mail_settings(payload if isinstance(payload, dict) else None)
        if str(settings.get("delivery_mode", "smtp")).strip().lower() == "local":
            if not str(settings.get("mail_from", "")).strip():
                settings["mail_from"] = "noreply@local.test"
            if not str(settings.get("local_outbox_dir", "")).strip():
                settings["local_outbox_dir"] = str(MAIL_OUTBOX_DIR)
        return settings

    def persist_auth_settings(self) -> None:
        email = self.email_var.get().strip()
        history = [item for item in self.login_history if item != email]
        if email:
            history.insert(0, email)
        history = history[:12]

        remembered_accounts = dict(self.remembered_accounts)
        if email:
            if self.remember_password_var.get():
                remembered_accounts[email] = self.password_var.get()
            else:
                remembered_accounts.pop(email, None)

        payload = {
            "email": email,
            "remember_password": bool(self.remember_password_var.get()),
            "login_history": history,
            "remembered_accounts": remembered_accounts,
        }
        save_auth_settings(AUTH_SETTINGS_PATH, payload)
        self.auth_settings = payload
        self.login_history = history
        self.remembered_accounts = remembered_accounts

    def on_email_selected(self, _event: object | None = None) -> None:
        email = self.email_var.get().strip()
        if email in self.remembered_accounts:
            self.password_var.set(self.remembered_accounts[email])
            self.remember_password_var.set(True)
        else:
            self.password_var.set("")
            self.remember_password_var.set(False)

    def try_login(self) -> None:
        email = self.email_var.get().strip()
        password = self.password_var.get()

        if not email:
            self.status_var.set(self.t("login_missing_email"))
            return
        if not self.validate_email_fn(email):
            self.status_var.set(self.t("login_invalid_email"))
            return
        if not password:
            self.status_var.set(self.t("login_missing_password"))
            return

        user = authenticate_user_via_api(self.server_settings, email, password)
        if user is None:
            self.status_var.set(self.t("login_invalid_credentials"))
            return

        self.persist_auth_settings()
        self.result = user
        self.window.destroy()

    def open_register_dialog(self) -> None:
        dialog = tk.Toplevel(self.window)
        dialog.title(self.t("register_title"))
        dialog.minsize(460, 520)
        dialog.resizable(False, False)
        dialog.transient(self.window)
        dialog.grab_set()

        display_name_var = tk.StringVar()
        email_var = tk.StringVar()
        password_var = tk.StringVar()
        confirm_var = tk.StringVar()
        status_var = tk.StringVar(value="")

        frame = ttk.Frame(dialog, padding=20)
        frame.pack(fill="both", expand=True)
        frame.columnconfigure(0, weight=1)

        ttk.Label(frame, text=self.t("register_title"), font=("Segoe UI", 14, "bold")).grid(row=0, column=0, sticky="w")
        ttk.Label(frame, text=self.t("register_pending_notice"), style="Muted.TLabel", wraplength=360, justify="left").grid(
            row=1,
            column=0,
            sticky="w",
            pady=(8, 10),
        )
        ttk.Label(frame, text=self.t("register_display_name")).grid(row=2, column=0, sticky="w")
        ttk.Entry(frame, textvariable=display_name_var).grid(row=3, column=0, sticky="ew", pady=(6, 10))
        ttk.Label(frame, text=self.t("login_email")).grid(row=4, column=0, sticky="w")
        ttk.Entry(frame, textvariable=email_var).grid(row=5, column=0, sticky="ew", pady=(6, 10))
        ttk.Label(frame, text=self.t("login_password")).grid(row=6, column=0, sticky="w")
        ttk.Entry(frame, textvariable=password_var, show="*").grid(row=7, column=0, sticky="ew", pady=(6, 10))
        ttk.Label(frame, text=self.t("register_confirm_password")).grid(row=8, column=0, sticky="w")
        ttk.Entry(frame, textvariable=confirm_var, show="*").grid(row=9, column=0, sticky="ew", pady=(6, 10))
        ttk.Label(frame, textvariable=status_var, foreground="#8a4b1f", wraplength=360).grid(row=10, column=0, sticky="w", pady=(0, 10))

        def submit() -> None:
            email = email_var.get().strip()
            password = password_var.get()
            confirm_password = confirm_var.get()
            if not email:
                status_var.set(self.t("login_missing_email"))
                return
            if not self.validate_email_fn(email):
                status_var.set(self.t("login_invalid_email"))
                return
            if len(password) < 8:
                status_var.set(self.t("register_password_short"))
                return
            if password != confirm_password:
                status_var.set(self.t("register_password_mismatch"))
                return
            try:
                register_user_via_api(
                    self.server_settings,
                    email=email,
                    password=password,
                    display_name=display_name_var.get().strip(),
                )
            except Exception as exc:
                status_var.set(str(exc))
                return

            self.email_var.set(email)
            self.password_var.set("")
            self.remember_password_var.set(False)
            self.status_var.set(self.t("register_success"))
            dialog.destroy()

        ttk.Button(frame, text=self.t("register_button"), command=submit).grid(row=11, column=0, sticky="ew")
        dialog.bind("<Return>", lambda _event: submit())
        self.center_dialog(dialog, min_width=460, min_height=520)

    def open_forgot_password_dialog(self) -> None:
        dialog = tk.Toplevel(self.window)
        dialog.title(self.t("forgot_password_title"))
        dialog.minsize(430, 250)
        dialog.resizable(False, False)
        dialog.transient(self.window)
        dialog.grab_set()

        email_var = tk.StringVar(value=self.email_var.get().strip())
        status_var = tk.StringVar(value="")

        frame = ttk.Frame(dialog, padding=20)
        frame.pack(fill="both", expand=True)
        frame.columnconfigure(0, weight=1)

        ttk.Label(frame, text=self.t("forgot_password_title"), font=("Segoe UI", 14, "bold")).grid(row=0, column=0, sticky="w")
        ttk.Label(frame, text=self.t("login_email")).grid(row=1, column=0, sticky="w", pady=(14, 0))
        ttk.Entry(frame, textvariable=email_var).grid(row=2, column=0, sticky="ew", pady=(6, 10))
        ttk.Label(frame, textvariable=status_var, foreground="#8a4b1f", wraplength=360).grid(row=3, column=0, sticky="w", pady=(0, 10))

        def submit() -> None:
            email = email_var.get().strip()
            if not email:
                status_var.set(self.t("login_missing_email"))
                return
            if not self.validate_email_fn(email):
                status_var.set(self.t("login_invalid_email"))
                return
            user = fetch_user_by_email(self.server_settings, email)
            if user is None:
                status_var.set(self.t("login_invalid_credentials"))
                return
            if str(user.get("approval_status", "approved")).strip().lower() != "approved":
                status_var.set(self.t("forgot_password_pending_approval"))
                return
            try:
                mail_settings = self.load_mail_settings_from_db()
                if (
                    str(mail_settings.get("delivery_mode", "smtp")).strip().lower() != "local"
                    and not str(mail_settings.get("smtp_host", "")).strip()
                ):
                    status_var.set(self.t("forgot_password_mail_missing"))
                    return
                expires_at = (datetime.now(timezone.utc) + timedelta(minutes=60)).strftime("%Y-%m-%d %H:%M:%S")
                token_response = create_password_reset_token_via_api(
                    self.server_settings,
                    email=email,
                    expires_at=expires_at,
                )
                token = str(token_response.get("token", "")).strip()
                reset_base_url = str(mail_settings.get("reset_link_base_url", "")).strip()
                app_reset_link = f"poapp://reset-password?token={token}"
                server_settings = self.load_server_settings_from_db()
                api_base_url = str(server_settings.get("api_base_url", DEFAULT_SERVER_SETTINGS["api_base_url"])).rstrip("/")
                open_in_app_link = f"{api_base_url}/auth/open-in-app?token={token}"
                if not reset_base_url:
                    reset_base_url = f"{api_base_url}/auth/reset-password"
                web_reset_link = f"{reset_base_url}?token={token}"
                send_email(
                    mail_settings,
                    to_email=email,
                    subject=self.t("forgot_password_reset_subject"),
                    body_text=(
                        f"{self.t('forgot_password_reset_subject')}\n\n"
                        f"Open in app: {open_in_app_link}\n"
                        f"Direct app link: {app_reset_link}\n"
                        f"Open in browser: {web_reset_link}\n\n"
                        "This link expires in 60 minutes."
                    ),
                    body_html=(
                        "<html><body style='font-family:Segoe UI,Arial,sans-serif'>"
                        f"<p>{self.t('forgot_password_reset_subject')}</p>"
                        f"<p><a href='{open_in_app_link}'>Open reset form in CitiFox ERP App</a></p>"
                        f"<p><a href='{web_reset_link}'>Open reset form in browser</a></p>"
                        "<p>This link expires in 60 minutes.</p>"
                        "</body></html>"
                    ),
                )
            except Exception as exc:
                status_var.set(str(exc))
                return

            self.email_var.set(email)
            self.status_var.set(self.t("forgot_password_success"))
            dialog.destroy()

        ttk.Button(frame, text=self.t("forgot_password_button"), command=submit).grid(row=4, column=0, sticky="ew")
        dialog.bind("<Return>", lambda _event: submit())
        self.center_dialog(dialog, min_width=430, min_height=250)

    def open_reset_token_dialog(self, token: str) -> None:
        token_value = str(token or "").strip()
        token_row = get_password_reset_token_via_api(self.server_settings, token_value)
        if token_row is None:
            messagebox.showerror(self.t("forgot_password_title"), "Reset token is invalid.")
            return

        dialog = tk.Toplevel(self.window)
        dialog.title(self.t("forgot_password_title"))
        dialog.minsize(460, 320)
        dialog.resizable(False, False)
        dialog.transient(self.window)
        dialog.grab_set()

        password_var = tk.StringVar()
        confirm_var = tk.StringVar()
        status_var = tk.StringVar(value="")

        frame = ttk.Frame(dialog, padding=20)
        frame.pack(fill="both", expand=True)
        frame.columnconfigure(0, weight=1)

        ttk.Label(frame, text=self.t("forgot_password_title"), font=("Segoe UI", 14, "bold")).grid(row=0, column=0, sticky="w")
        ttk.Label(
            frame,
            text=f"{self.t('login_email')}: {str(token_row.get('email', '')).strip()}",
            style="Muted.TLabel",
            wraplength=380,
            justify="left",
        ).grid(row=1, column=0, sticky="w", pady=(8, 12))
        ttk.Label(frame, text="New Password").grid(row=2, column=0, sticky="w")
        ttk.Entry(frame, textvariable=password_var, show="*").grid(row=3, column=0, sticky="ew", pady=(6, 10))
        ttk.Label(frame, text="Confirm Password").grid(row=4, column=0, sticky="w")
        ttk.Entry(frame, textvariable=confirm_var, show="*").grid(row=5, column=0, sticky="ew", pady=(6, 10))
        ttk.Label(frame, textvariable=status_var, foreground="#8a4b1f", wraplength=380).grid(row=6, column=0, sticky="w", pady=(0, 12))

        def submit() -> None:
            password = password_var.get()
            confirm_password = confirm_var.get()
            if len(password) < 8:
                status_var.set("Password must be at least 8 characters.")
                return
            if password != confirm_password:
                status_var.set(self.t("register_password_mismatch"))
                return
            try:
                update_password_with_token_via_api(
                    self.server_settings,
                    token=token_value,
                    password=password,
                )
            except Exception as exc:
                status_var.set(str(exc))
                return
            self.email_var.set(str(token_row.get("email", "")).strip())
            self.password_var.set("")
            self.status_var.set("Password updated successfully. Please log in with your new password.")
            dialog.destroy()

        ttk.Button(frame, text="Update Password", command=submit).grid(row=7, column=0, sticky="ew")
        dialog.bind("<Return>", lambda _event: submit())
        self.center_dialog(dialog, min_width=460, min_height=320)

    def on_close(self) -> None:
        self.result = None
        self.window.destroy()

    def show(self) -> dict | None:
        self.window.mainloop()
        return self.result
