"""
CalibrationTypeDialog - диалог для конкретного типа калибровки (tkinter)
"""

import tkinter as tk
from tkinter import ttk, messagebox
import shutil
import re
from pathlib import Path
from datetime import datetime

from services.file_service import FileService
from services.watch_service import WatchService


class CalibrationTypeDialog(tk.Toplevel):
    """Диалог для съёмки калибровочных кадров определённого типа"""

    def __init__(self, parent, cal_type, base_folder, camera_name, config_service):
        super().__init__(parent)
        self.parent = parent
        self.cal_type = cal_type
        self.base_folder = Path(base_folder)
        self.camera_name = camera_name
        self.config_service = config_service

        self.is_capturing = False
        self.current_count = 0
        self.target_count = 0
        self._watch_service = None

        self.settings = self._get_default_settings()

        self.title(self._get_title())
        self.geometry("600x650")
        self.minsize(550, 600)
        self.transient(parent)
        self.grab_set()

        self._create_ui()
        self._load_optics()
        self._update_recommendations()
        self._center_window()

    def _get_title(self):
        titles = {
            'bias': 'BIAS (Bias Frames)',
            'dark': 'DARK (Dark Frames)',
            'flat': 'FLAT (Flat Fields)'
        }
        return titles.get(self.cal_type, 'Calibration Frames')

    def _get_default_settings(self):
        base = {
            'bias': {
                'iso_values': [800, 1600, 3200],
                'default_iso': 800,
                'count': 50,
                'min_count': 30,
                'max_count': 100,
                'recommended_count': 50,
            },
            'dark': {
                'iso_values': [800, 1600, 3200],
                'default_iso': 800,
                'exposure_values': [30, 60, 120, 180, 300],
                'default_exposure': 120,
                'count': 20,
                'min_count': 10,
                'max_count': 50,
                'recommended_count': 20,
            },
            'flat': {
                'iso_values': [800, 1600, 3200],
                'default_iso': 800,
                'aperture_values': ['f/2.8', 'f/4', 'f/5.6', 'f/8'],
                'count': 30,
                'min_count': 20,
                'max_count': 60,
                'recommended_count': 30,
            }
        }
        return base.get(self.cal_type, {})

    def _create_ui(self):
        # Main frame
        main_frame = ttk.Frame(self, padding="20")
        main_frame.pack(fill='both', expand=True)

        # Title with help button
        title_frame = ttk.Frame(main_frame)
        title_frame.pack(fill='x', pady=(0, 10))

        ttk.Label(title_frame, text=self._get_title(), font=('Segoe UI', 16, 'bold')).pack(side='left')

        help_btn = ttk.Button(title_frame, text="?", width=3, command=self._show_help)
        help_btn.pack(side='right')

        # Parameters frame
        params_frame = ttk.LabelFrame(main_frame, text="Camera Settings", padding="10")
        params_frame.pack(fill='x', pady=10)

        # ISO
        iso_row = ttk.Frame(params_frame)
        iso_row.pack(fill='x', pady=5)
        ttk.Label(iso_row, text="ISO:", width=15).pack(side='left')
        self.iso_combo = ttk.Combobox(iso_row, values=self.settings['iso_values'], width=10)
        self.iso_combo.set(str(self.settings['default_iso']))
        self.iso_combo.pack(side='left', padx=5)
        self.iso_combo.bind('<<ComboboxSelected>>', lambda e: self._update_recommendations())

        ttk.Button(iso_row, text="Custom", width=8, command=self._add_custom_iso).pack(side='left', padx=5)

        # Exposure (only for DARK)
        if self.cal_type == 'dark':
            exp_row = ttk.Frame(params_frame)
            exp_row.pack(fill='x', pady=5)
            ttk.Label(exp_row, text="Exposure (sec):", width=15).pack(side='left')
            self.exposure_combo = ttk.Combobox(exp_row, values=self.settings['exposure_values'], width=10)
            self.exposure_combo.set(str(self.settings['default_exposure']))
            self.exposure_combo.pack(side='left', padx=5)
            self.exposure_combo.bind('<<ComboboxSelected>>', lambda e: self._update_recommendations())
            ttk.Button(exp_row, text="Custom", width=8, command=self._add_custom_exposure).pack(side='left', padx=5)

        # Optics (only for FLAT)
        if self.cal_type == 'flat':
            optics_row = ttk.Frame(params_frame)
            optics_row.pack(fill='x', pady=5)
            ttk.Label(optics_row, text="Optics:", width=15).pack(side='left')
            self.optics_combo = ttk.Combobox(optics_row, width=30)
            self.optics_combo.pack(side='left', fill='x', expand=True, padx=5)

            aperture_row = ttk.Frame(params_frame)
            aperture_row.pack(fill='x', pady=5)
            ttk.Label(aperture_row, text="Aperture:", width=15).pack(side='left')
            self.aperture_combo = ttk.Combobox(aperture_row, values=self.settings['aperture_values'], width=10)
            self.aperture_combo.set('f/5.6')
            self.aperture_combo.pack(side='left', padx=5)

            ttk.Label(aperture_row, text="(Fixed for telescopes)", foreground='#888888').pack(side='left', padx=10)

        # Count
        count_row = ttk.Frame(params_frame)
        count_row.pack(fill='x', pady=5)
        ttk.Label(count_row, text="Number of frames:", width=15).pack(side='left')

        self.count_spin = tk.Spinbox(count_row, from_=self.settings['min_count'], to=self.settings['max_count'],
                                      width=8, font=('Segoe UI', 10))
        self.count_spin.delete(0, 'end')
        self.count_spin.insert(0, str(self.settings['count']))
        self.count_spin.pack(side='left', padx=5)

        ttk.Label(count_row, text=f"(recommended: {self.settings['recommended_count']})", foreground='#888888').pack(side='left', padx=5)

        # Recommendations frame
        tips_frame = tk.Frame(main_frame, bg='#2d2d2d', relief='groove', bd=1)
        tips_frame.pack(fill='x', pady=10)

        self.tips_text = tk.Text(tips_frame, height=12, wrap='word', bg='#2d2d2d', fg='#FFD700',
                                  font=('Segoe UI', 9), relief='flat', padx=10, pady=10)
        self.tips_text.pack(fill='both', expand=True)

        # Progress frame
        self.progress_frame = ttk.LabelFrame(main_frame, text="Progress", padding="10")

        self.progress_bar = ttk.Progressbar(self.progress_frame, orient='horizontal', length=400, mode='determinate')
        self.progress_bar.pack(pady=5)

        self.progress_label = ttk.Label(self.progress_frame, text="Ready to shoot")
        self.progress_label.pack()

        # Save path
        save_frame = ttk.Frame(main_frame)
        save_frame.pack(fill='x', pady=10)

        ttk.Label(save_frame, text="Save to:", font=('Segoe UI', 9, 'bold')).pack(anchor='w')
        self.save_path_label = ttk.Label(save_frame, foreground='#4CAF50')
        self.save_path_label.pack(anchor='w')

        # Buttons
        btn_frame = ttk.Frame(main_frame)
        btn_frame.pack(pady=10)

        self.back_btn = ttk.Button(btn_frame, text="Back", command=self._on_back)
        self.back_btn.pack(side='left', padx=5)

        self.start_btn = ttk.Button(btn_frame, text="Start Shooting", command=self._start_capture, style='Green.TButton')
        self.start_btn.pack(side='left', padx=5)

        self.stop_btn = ttk.Button(btn_frame, text="Stop", command=self._on_stop, style='Red.TButton')
        self.stop_btn.pack(side='left', padx=5)
        self.stop_btn.config(state='disabled')

        self._update_save_path()

    def _load_optics(self):
        if self.cal_type != 'flat':
            return

        lenses = self.config_service.get_lenses()
        telescopes = self.config_service.get_telescopes()

        all_optics = lenses + telescopes

        self.optics_combo['values'] = all_optics
        if all_optics:
            self.optics_combo.set(all_optics[0])

        def on_optics_change(*args):
            current = self.optics_combo.get()
            if 'f/' in current:
                self.aperture_combo.config(state='disabled')
                match = re.search(r'f/(\d+\.?\d*)', current)
                if match:
                    self.aperture_combo.set(f"f/{match.group(1)}")
            else:
                self.aperture_combo.config(state='normal')

        self.optics_combo.bind('<<ComboboxSelected>>', on_optics_change)

    def _update_save_path(self):
        iso = int(self.iso_combo.get())

        if self.cal_type == 'bias':
            path = self.base_folder / "Calibration" / self.camera_name / "Bias" / f"ISO{iso}"
        elif self.cal_type == 'dark':
            exposure = self.exposure_combo.get()
            path = self.base_folder / "Calibration" / self.camera_name / "Dark" / f"ISO{iso}_{exposure}s"
        elif self.cal_type == 'flat':
            optics = self.optics_combo.get()
            optics_name = optics
            invalid_chars = '<>:"/\\|?*'
            for char in invalid_chars:
                optics_name = optics_name.replace(char, '_')
            date_str = datetime.now().strftime("%Y-%m-%d")
            path = self.base_folder / "Calibration" / self.camera_name / "Flat" / optics_name / date_str
        else:
            path = self.base_folder

        self.save_path_label.config(text=str(path))
        return path

    def _update_recommendations(self):
        self.tips_text.config(state='normal')
        self.tips_text.delete('1.0', 'end')

        if self.cal_type == 'bias':
            self.tips_text.insert('1.0',
                "BIAS (Bias Frames)\n\n"
                "HOW TO SHOOT:\n"
                "• Close lens cap\n"
                "• Shutter speed: FASTEST (1/4000 or 1/8000)\n"
                "• ISO: same as light frames\n\n"
                "TIPS:\n"
                "• Can be taken at home anytime\n"
                "• Works for all lenses/telescopes\n"
                "• 50 frames recommended")
        elif self.cal_type == 'dark':
            self.tips_text.insert('1.0',
                "DARK (Dark Frames)\n\n"
                "IMPORTANT: Shoot AFTER session on site!\n\n"
                "HOW TO SHOOT:\n"
                "• Close lens cap\n"
                "• SAME ISO and exposure as light frames\n"
                "• Wait for camera to reach night temperature\n\n"
                "TEMPERATURE:\n"
                "• Shoot at the SAME temperature as light frames\n"
                "• Difference >5C makes darks useless!\n"
                "• Best taken immediately after session")
        elif self.cal_type == 'flat':
            self.tips_text.insert('1.0',
                "FLAT (Flat Fields)\n\n"
                "IMPORTANT: DON'T change focus or zoom!\n\n"
                "HOW TO SHOOT:\n"
                "• Method 1: LED panel (recommended)\n"
                "• Method 2: Dawn/dusk sky, point at zenith\n"
                "• Method 3: White T-shirt over lens\n\n"
                "GOAL:\n"
                "• Remove vignetting and dust\n"
                "• Histogram at 50-70%\n"
                "• 30 frames recommended")

        self.tips_text.config(state='disabled')
        self._update_save_path()

    def _start_capture(self):
        self.target_count = int(self.count_spin.get())
        self.current_count = 0

        target_folder = self._update_save_path()

        try:
            target_folder.mkdir(parents=True, exist_ok=True)
        except Exception as e:
            messagebox.showerror("Error", f"Failed to create folder:\n{target_folder}\n\n{str(e)}", parent=self)
            return

        # Show progress frame
        self.progress_frame.pack(fill='x', pady=10)
        self.progress_bar['maximum'] = self.target_count
        self.progress_bar['value'] = 0
        self.progress_label.config(text=f"0 / {self.target_count} frames")

        # Disable controls
        self.start_btn.config(state='disabled')
        self.stop_btn.config(state='normal')
        self.back_btn.config(state='disabled')
        self.iso_combo.config(state='disabled')
        self.count_spin.config(state='disabled')
        if hasattr(self, 'exposure_combo'):
            self.exposure_combo.config(state='disabled')
        if hasattr(self, 'optics_combo'):
            self.optics_combo.config(state='disabled')
            self.aperture_combo.config(state='disabled')

        self.is_capturing = True

        # Get watch folder from main window
        watch_folder = self._get_watch_folder()
        if not watch_folder:
            messagebox.showerror("Error", "Could not determine watch folder!\nPlease select a folder in the main window.", parent=self)
            self._stop_capture()
            return

        if not watch_folder.exists():
            messagebox.showerror("Error", f"Watch folder does not exist:\n{watch_folder}", parent=self)
            self._stop_capture()
            return

        FileService.clear_watch_folder(watch_folder)

        self._watch_service = WatchService()

        def on_file_received(file_path):
            if not self.is_capturing:
                return

            if self._process_calibration_file(file_path, target_folder):
                self.current_count += 1
                self.after(0, self._update_progress)

                if self.current_count >= self.target_count:
                    self.after(0, self._stop_capture)
                    self.after(0, lambda: messagebox.showinfo("Success", f"Capture completed!\nSaved {self.current_count} frames to:\n{target_folder}", parent=self))
                    self.after(100, lambda: self.progress_frame.pack_forget())

        success = self._watch_service.start(watch_folder, on_file_received)
        if not success:
            messagebox.showerror("Error", "Failed to start watching folder!", parent=self)
            self._stop_capture()
            return

        self.progress_label.config(text=f"Watching: {watch_folder}\nWaiting for files...")

    def _update_progress(self):
        self.progress_bar['value'] = self.current_count
        self.progress_label.config(text=f"{self.current_count} / {self.target_count} frames")

    def _process_calibration_file(self, file_path, target_folder):
        if not FileService.is_photo(file_path):
            return False

        try:
            target_folder.mkdir(parents=True, exist_ok=True)

            timestamp = datetime.now()
            date_str = timestamp.strftime("%Y-%m-%d")
            time_str = timestamp.strftime("%H-%M-%S")
            suffix = file_path.suffix

            if self.cal_type == 'bias':
                iso = self.iso_combo.get()
                prefix = f"Bias_{self.camera_name}_ISO{iso}"
            elif self.cal_type == 'dark':
                iso = self.iso_combo.get()
                exposure = self.exposure_combo.get()
                prefix = f"Dark_{self.camera_name}_ISO{iso}_{exposure}s"
            elif self.cal_type == 'flat':
                optics = self.optics_combo.get()
                optics_name = optics
                invalid_chars = '<>:"/\\|?*'
                for char in invalid_chars:
                    optics_name = optics_name.replace(char, '_')
                aperture = self.aperture_combo.get()
                prefix = f"Flat_{optics_name}_{aperture}"
            else:
                prefix = "Calibration"

            for char in '<>:"/\\|?*':
                prefix = prefix.replace(char, '_')

            new_filename = f"{prefix}_{date_str}_{time_str}{suffix}"
            target_path = target_folder / new_filename
            target_path = FileService.resolve_conflict(target_path)

            shutil.move(str(file_path), str(target_path))
            return True

        except Exception as e:
            print(f"Error saving {file_path.name}: {e}")
            return False

    def _stop_capture(self):
        self.is_capturing = False

        if self._watch_service:
            self._watch_service.stop()
            self._watch_service = None

        self.start_btn.config(state='normal')
        self.stop_btn.config(state='disabled')
        self.back_btn.config(state='normal')
        self.iso_combo.config(state='normal')
        self.count_spin.config(state='normal')
        if hasattr(self, 'exposure_combo'):
            self.exposure_combo.config(state='normal')
        if hasattr(self, 'optics_combo'):
            self.optics_combo.config(state='normal')
            self.aperture_combo.config(state='normal')

        self.progress_label.config(text="Capture stopped")

    def _on_back(self):
        if self.is_capturing:
            messagebox.showwarning("Warning", "Please stop the capture first!", parent=self)
            return
        self.destroy()

    def _on_stop(self):
        if self.current_count < self.target_count and self.current_count > 0:
            reply = messagebox.askyesno("Stop Capture",
                f"You haven't finished capture ({self.current_count} of {self.target_count} frames).\n"
                f"Are you sure you want to stop?",
                parent=self)
            if reply:
                self._stop_capture()
        elif self.current_count == 0:
            reply = messagebox.askyesno("Stop Capture",
                "Capture hasn't started yet. Are you sure?",
                parent=self)
            if reply:
                self._stop_capture()
        else:
            self._stop_capture()

    def _get_watch_folder(self):
        # Traverse to find main window's folder_entry
        parent = self.parent
        while parent:
            if hasattr(parent, 'folder_entry'):
                watch_folder = parent.folder_entry.get()
                if watch_folder and watch_folder != "Select watch folder...":
                    return Path(watch_folder)
            parent = parent.master if hasattr(parent, 'master') else parent.parent if hasattr(parent, 'parent') else None
        return None

    def _add_custom_iso(self):
        dialog = tk.Toplevel(self)
        dialog.title("Custom ISO")
        dialog.geometry("300x100")
        dialog.transient(self)
        dialog.grab_set()

        ttk.Label(dialog, text="Enter ISO value:").pack(pady=10)
        entry = ttk.Entry(dialog)
        entry.pack(pady=5)
        entry.focus()

        def save():
            try:
                value = int(entry.get())
                if 100 <= value <= 12800:
                    iso_str = str(value)
                    values = list(self.iso_combo['values'])
                    if iso_str not in values:
                        values.append(iso_str)
                        values.sort(key=int)
                        self.iso_combo['values'] = values
                    self.iso_combo.set(iso_str)
                    dialog.destroy()
                else:
                    messagebox.showerror("Error", "ISO must be between 100 and 12800", parent=dialog)
            except ValueError:
                messagebox.showerror("Error", "Please enter a valid number", parent=dialog)

        ttk.Button(dialog, text="OK", command=save).pack(pady=10)
        dialog.bind('<Return>', lambda e: save())

    def _add_custom_exposure(self):
        dialog = tk.Toplevel(self)
        dialog.title("Custom Exposure")
        dialog.geometry("300x100")
        dialog.transient(self)
        dialog.grab_set()

        ttk.Label(dialog, text="Enter exposure (seconds):").pack(pady=10)
        entry = ttk.Entry(dialog)
        entry.pack(pady=5)
        entry.focus()

        def save():
            try:
                value = int(entry.get())
                if 1 <= value <= 3600:
                    exp_str = str(value)
                    values = list(self.exposure_combo['values'])
                    if exp_str not in values:
                        values.append(exp_str)
                        values.sort(key=int)
                        self.exposure_combo['values'] = values
                    self.exposure_combo.set(exp_str)
                    dialog.destroy()
                else:
                    messagebox.showerror("Error", "Exposure must be between 1 and 3600 seconds", parent=dialog)
            except ValueError:
                messagebox.showerror("Error", "Please enter a valid number", parent=dialog)

        ttk.Button(dialog, text="OK", command=save).pack(pady=10)
        dialog.bind('<Return>', lambda e: save())

    def _show_help(self):
        if self.cal_type == 'bias':
            help_text = (
                "What are BIAS frames?\n\n"
                "Bias frames are shots with the lens cap closed\n"
                "at the shortest possible shutter speed.\n\n"
                "Why they are needed:\n"
                "• Remove read noise\n"
                "• Correct black level offset\n\n"
                "How many to take:\n"
                "• 50 frames for good averaging\n"
                "• Can be used for a whole month\n\n"
                "When to take:\n"
                "• At home anytime\n"
                "• Temperature doesn't matter"
            )
        elif self.cal_type == 'dark':
            help_text = (
                "What are DARK frames?\n\n"
                "Dark frames are shots with the lens cap closed\n"
                "with the SAME ISO and exposure as light frames.\n\n"
                "Why they are needed:\n"
                "• Remove thermal noise\n"
                "• Remove hot pixels\n\n"
                "IMPORTANT about temperature:\n"
                "• Take AFTER the session on site!\n"
                "• Camera must be at the same temperature\n"
                "• Difference >5C makes darks useless!"
            )
        else:
            help_text = (
                "What are FLAT frames?\n\n"
                "Flat frames are shots of an evenly lit surface\n"
                "with the SAME focus and zoom.\n\n"
                "Why they are needed:\n"
                "• Remove lens vignetting\n"
                "• Remove dust on sensor/lens\n\n"
                "How to shoot:\n"
                "1. LED panel (best option)\n"
                "2. Dawn/dusk sky, point at zenith\n"
                "3. White T-shirt over lens\n\n"
                "IMPORTANT:\n"
                "• DON'T change focus!\n"
                "• DON'T change zoom!\n"
                "• Take at the end of the session"
            )

        messagebox.showinfo("Help", help_text, parent=self)

    def _center_window(self):
        self.update_idletasks()
        x = self.parent.winfo_x() + (self.parent.winfo_width() // 2) - (self.winfo_width() // 2)
        y = self.parent.winfo_y() + (self.parent.winfo_height() // 2) - (self.winfo_height() // 2)
        self.geometry(f'+{x}+{y}')