"""
설정 뷰
"""
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
                             QPushButton, QSpinBox, QCheckBox, QGroupBox,
                             QComboBox, QTimeEdit, QMessageBox, QFileDialog,
                             QScrollArea, QWidget, QLineEdit)
from PyQt5.QtCore import Qt, QTime
from PyQt5.QtWidgets import QApplication
from datetime import datetime
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from core.database import Database
from core.i18n import tr
from core.settings_keys import (
    WORK_HOURS, WORK_MINUTES, LUNCH_DURATION_MINUTES, DINNER_DURATION_MINUTES,
    AUTO_LUNCH, AUTO_BREAK_ON_LOCK, AUTO_OVERTIME,
    NOTIF_CLOCK_OUT, NOTIF_LUNCH, NOTIF_DINNER, NOTIF_OVERTIME, NOTIF_HEALTH,
    NOTIFICATION_BEFORE_MINUTES,
    LUNCH_REMINDER_HOURS, DINNER_REMINDER_HOURS,
    OVERTIME_THRESHOLD_HOURS, WEEKLY_HOURS_THRESHOLD, HEALTH_CONSECUTIVE_OT_DAYS,
    HEALTH_BREAK_HOURS, HEALTH_BREAK_ENABLED,
    THEME, TIME_FORMAT, LANGUAGE, OVERTIME_UNIT, ANNUAL_LEAVE_DAYS,
    INITIAL_OVERTIME_MINUTES, INITIAL_LEAVE_USED_HOURS,
    DB_PATH_OVERRIDE, CLOCK_IN_ON_UNLOCK,
    GOAL_OVERTIME_MAX_MONTHLY, GOAL_AVG_HOURS_DAILY,
    GITEA_FEEDBACK_TOKEN, GITEA_FEEDBACK_ENABLED,
    FONT_SCALE, HIGH_CONTRAST,
)
from utils.csv_exporter import CSVExporter
from ui.leave_view import AddLeaveDialog
from ui.styles import apply_dark_titlebar


class SettingsView(QDialog):
    """설정 다이얼로그"""

    def __init__(self, parent=None, db=None):
        super().__init__(parent)
        self.db = db if db else Database()
        self.parent_window = parent
        self.init_ui()
        self.load_settings()
        self._settings_loaded = True
        apply_dark_titlebar(self)

    def init_ui(self):
        """UI 초기화"""
        self.setWindowTitle(tr('window.settings'))
        self.setModal(True)
        self.setMinimumSize(600, 700)

        # 메인 레이아웃
        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)

        # 제목
        title = QLabel(tr('settings.title'))
        title.setObjectName("dialog_title")
        title.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(title)

        # 스크롤 영역
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        # 스크롤 내용
        scroll_content = QWidget()
        scroll_content.setObjectName("scroll_content")
        layout = QVBoxLayout()
        layout.setSpacing(16)
        layout.setContentsMargins(20, 10, 20, 10)

        # 근무 시간 설정
        work_time_group = self.create_work_time_group()
        layout.addWidget(work_time_group)

        # 알림 설정
        notification_group = self.create_notification_group()
        layout.addWidget(notification_group)

        # 연장근무 설정
        overtime_group = self.create_overtime_group()
        layout.addWidget(overtime_group)
        self.update_overtime_balance_display()

        # 휴가 설정
        leave_group = self.create_leave_group()
        layout.addWidget(leave_group)

        # 목표 설정 그룹
        goal_group = self.create_goal_group()
        layout.addWidget(goal_group)

        # 공휴일 설정
        holiday_group = self.create_holiday_group()
        layout.addWidget(holiday_group)

        # 데이터 관리
        data_group = self.create_data_group()
        layout.addWidget(data_group)

        layout.addStretch()

        scroll_content.setLayout(layout)
        scroll_area.setWidget(scroll_content)
        main_layout.addWidget(scroll_area)

        # 버튼들 (스크롤 영역 밖에)
        button_layout = QHBoxLayout()
        button_layout.setContentsMargins(20, 10, 20, 20)

        save_button = QPushButton(tr('btn.save'))
        save_button.setObjectName("btn_primary")
        save_button.setMinimumHeight(40)
        save_button.clicked.connect(self.save_settings)
        button_layout.addWidget(save_button)

        close_button = QPushButton(tr('btn.close'))
        close_button.setMinimumHeight(40)
        close_button.clicked.connect(self.close)
        button_layout.addWidget(close_button)

        main_layout.addLayout(button_layout)

        self.setLayout(main_layout)

    def create_work_time_group(self) -> QGroupBox:
        """근무 시간 설정 그룹"""
        group = QGroupBox(tr('group.work_time'))
        layout = QVBoxLayout()
        layout.setSpacing(8)

        # 근무 패턴 프리셋
        preset_layout = QHBoxLayout()
        preset_label = QLabel(tr('settings.work_pattern'))
        preset_label.setFixedWidth(130)
        self.work_preset_combo = QComboBox()
        # (label, work_minutes, lunch_minutes)
        self.work_preset_combo.addItem(tr('settings.preset.standard_8h'), (480, 60))
        self.work_preset_combo.addItem(tr('settings.preset.short_7h30m'), (450, 30))
        self.work_preset_combo.addItem(tr('settings.preset.short_7h'), (420, 60))
        self.work_preset_combo.addItem(tr('settings.preset.short_6h'), (360, 30))
        self.work_preset_combo.addItem(tr('settings.preset.half_4h'), (240, 0))
        self.work_preset_combo.addItem(tr('settings.preset.custom'), None)
        self.work_preset_combo.setFixedWidth(260)
        self.work_preset_combo.currentIndexChanged.connect(self.on_preset_changed)
        preset_layout.addWidget(preset_label)
        preset_layout.addWidget(self.work_preset_combo)
        preset_layout.addStretch()
        layout.addLayout(preset_layout)

        # 하루 기본 근무 시간 (시 + 분 분리 입력)
        work_hours_layout = QHBoxLayout()
        work_hours_label = QLabel(tr('settings.daily_work'))
        work_hours_label.setFixedWidth(130)
        self.work_hours_spin = QSpinBox()
        self.work_hours_spin.setRange(0, 12)
        self.work_hours_spin.setValue(8)
        self.work_hours_spin.setSuffix(tr('settings.suffix_hour'))
        self.work_hours_spin.setFixedWidth(100)
        self.work_minutes_spin = QSpinBox()
        self.work_minutes_spin.setRange(0, 59)
        self.work_minutes_spin.setValue(0)
        self.work_minutes_spin.setSingleStep(15)
        self.work_minutes_spin.setSuffix(tr('settings.suffix_minute'))
        self.work_minutes_spin.setFixedWidth(100)
        # 사용자가 시간/분 직접 변경 시 프리셋을 "사용자 정의"로
        self.work_hours_spin.valueChanged.connect(self._on_work_time_user_edit)
        self.work_minutes_spin.valueChanged.connect(self._on_work_time_user_edit)
        work_hours_layout.addWidget(work_hours_label)
        work_hours_layout.addWidget(self.work_hours_spin)
        work_hours_layout.addWidget(self.work_minutes_spin)
        work_hours_layout.addStretch()
        layout.addLayout(work_hours_layout)

        # 점심시간 기본값
        lunch_layout = QHBoxLayout()
        lunch_label = QLabel(tr('settings.lunch_default'))
        lunch_label.setFixedWidth(130)
        self.lunch_spin = QSpinBox()
        self.lunch_spin.setRange(0, 120)
        self.lunch_spin.setValue(60)
        self.lunch_spin.setSingleStep(5)
        self.lunch_spin.setSuffix(tr('settings.suffix_minute'))
        self.lunch_spin.setFixedWidth(110)
        self.lunch_spin.valueChanged.connect(self._on_work_time_user_edit)
        lunch_layout.addWidget(lunch_label)
        lunch_layout.addWidget(self.lunch_spin)

        # 점심시간 자동 적용
        self.auto_lunch_check = QCheckBox(tr('settings.auto_apply'))
        self.auto_lunch_check.setToolTip(tr('settings.auto_apply_tooltip'))
        lunch_layout.addWidget(self.auto_lunch_check)
        lunch_layout.addStretch()
        layout.addLayout(lunch_layout)

        # 저녁시간 기본값
        dinner_layout = QHBoxLayout()
        dinner_label = QLabel(tr('settings.dinner_default'))
        dinner_label.setFixedWidth(130)
        self.dinner_spin = QSpinBox()
        self.dinner_spin.setRange(0, 120)
        self.dinner_spin.setValue(60)
        self.dinner_spin.setSingleStep(5)
        self.dinner_spin.setSuffix(tr('settings.suffix_minute'))
        self.dinner_spin.setFixedWidth(110)
        dinner_layout.addWidget(dinner_label)
        dinner_layout.addWidget(self.dinner_spin)
        dinner_layout.addStretch()
        layout.addLayout(dinner_layout)

        group.setLayout(layout)
        return group

    def on_preset_changed(self, index):
        """근무 패턴 프리셋 변경 시 시간/분/점심 자동 입력"""
        data = self.work_preset_combo.itemData(index)
        if data is None:
            # 사용자 정의: 입력값 유지
            return
        work_minutes, lunch_minutes = data
        # 시그널 차단으로 _on_work_time_user_edit가 다시 프리셋을 바꾸지 않도록
        self.work_hours_spin.blockSignals(True)
        self.work_minutes_spin.blockSignals(True)
        self.lunch_spin.blockSignals(True)
        try:
            self.work_hours_spin.setValue(work_minutes // 60)
            self.work_minutes_spin.setValue(work_minutes % 60)
            self.lunch_spin.setValue(lunch_minutes)
        finally:
            self.work_hours_spin.blockSignals(False)
            self.work_minutes_spin.blockSignals(False)
            self.lunch_spin.blockSignals(False)

    def _on_work_time_user_edit(self, *_):
        """사용자가 시간/분/점심을 직접 수정하면 프리셋 콤보를 '사용자 정의'로 전환"""
        if not hasattr(self, '_settings_loaded'):
            return
        # 현재 값과 일치하는 프리셋이 있는지 확인
        current = (
            self.work_hours_spin.value() * 60 + self.work_minutes_spin.value(),
            self.lunch_spin.value()
        )
        for i in range(self.work_preset_combo.count()):
            data = self.work_preset_combo.itemData(i)
            if data == current:
                if self.work_preset_combo.currentIndex() != i:
                    self.work_preset_combo.blockSignals(True)
                    self.work_preset_combo.setCurrentIndex(i)
                    self.work_preset_combo.blockSignals(False)
                return
        # 일치하는 프리셋 없음 → 사용자 정의
        custom_index = self.work_preset_combo.count() - 1
        if self.work_preset_combo.currentIndex() != custom_index:
            self.work_preset_combo.blockSignals(True)
            self.work_preset_combo.setCurrentIndex(custom_index)
            self.work_preset_combo.blockSignals(False)

    def _load_threshold_safely(self, settings: dict, setting_key: str,
                                 attr_name: str, default: int) -> None:
        """settings dict에서 임계값을 읽어 SpinBox에 안전하게 적용.

        get_settings() 결과는 이미 타입 변환된 dict이라 추가 DB 호출 없이 사용.
        """
        if not hasattr(self, attr_name):
            return
        spin = getattr(self, attr_name)
        try:
            val = int(settings.get(setting_key, default))
        except (ValueError, TypeError):
            val = default
        # 이미 설정된 setRange를 존중 — 사용자 옛 값이 범위 밖이면 클램프
        spin.setValue(max(spin.minimum(), min(spin.maximum(), val)))

    @staticmethod
    def _make_threshold_spin(lo: int, hi: int, default: int, suffix: str) -> QSpinBox:
        """고급 임계값용 표준 SpinBox."""
        sb = QSpinBox()
        sb.setRange(lo, hi)
        sb.setValue(default)
        sb.setSuffix(suffix)
        sb.setFixedWidth(110)
        return sb

    @staticmethod
    def _labeled_row(label_text: str, widget) -> QHBoxLayout:
        """좌측 라벨(고정 폭) + 위젯 + 오른쪽 stretch 한 줄 레이아웃."""
        row = QHBoxLayout()
        lbl = QLabel(label_text)
        lbl.setFixedWidth(140)
        row.addWidget(lbl)
        row.addWidget(widget)
        row.addStretch()
        return row

    def create_notification_group(self) -> QGroupBox:
        """알림 설정 그룹"""
        group = QGroupBox(tr('group.notification'))
        layout = QVBoxLayout()
        layout.setSpacing(6)

        # 알림 체크박스들을 3행으로 배치 (저녁 알림 추가로 5개)
        check_row1 = QHBoxLayout()
        self.clock_out_notification_check = QCheckBox(tr('settings.notif_clock_out'))
        self.clock_out_notification_check.setChecked(True)
        self.lunch_notification_check = QCheckBox(tr('settings.notif_lunch'))
        self.lunch_notification_check.setChecked(True)
        check_row1.addWidget(self.clock_out_notification_check)
        check_row1.addWidget(self.lunch_notification_check)
        layout.addLayout(check_row1)

        check_row2 = QHBoxLayout()
        self.dinner_notification_check = QCheckBox(tr('settings.notif_dinner'))
        self.dinner_notification_check.setChecked(True)
        self.overtime_notification_check = QCheckBox(tr('settings.notif_overtime'))
        self.overtime_notification_check.setChecked(True)
        check_row2.addWidget(self.dinner_notification_check)
        check_row2.addWidget(self.overtime_notification_check)
        layout.addLayout(check_row2)

        check_row3 = QHBoxLayout()
        self.health_notification_check = QCheckBox(tr('settings.notif_health'))
        self.health_notification_check.setChecked(True)
        self.health_break_notification_check = QCheckBox(tr('settings.notif_break'))
        self.health_break_notification_check.setChecked(True)
        self.health_break_notification_check.setToolTip(
            tr('settings.notif_break_tooltip')
        )
        check_row3.addWidget(self.health_notification_check)
        check_row3.addWidget(self.health_break_notification_check)
        layout.addLayout(check_row3)

        # 퇴근 N분 전 알림 시점 설정
        before_row = QHBoxLayout()
        before_label = QLabel(tr('settings.notif_before'))
        before_label.setFixedWidth(110)
        self.notif_before_spin = QSpinBox()
        self.notif_before_spin.setRange(1, 120)
        self.notif_before_spin.setSingleStep(5)
        self.notif_before_spin.setValue(30)
        self.notif_before_spin.setSuffix(' ' + tr('settings.notif_before_spin_suffix'))
        self.notif_before_spin.setFixedWidth(110)
        self.notif_before_spin.setToolTip(tr('settings.notif_before_tooltip'))
        before_row.addWidget(before_label)
        before_row.addWidget(self.notif_before_spin)
        before_row.addStretch()
        layout.addLayout(before_row)

        # === 고급 임계값 (접이식 그룹박스) ===
        adv_box = QGroupBox(tr('settings.advanced_thresholds'))
        adv_box.setCheckable(True)
        adv_box.setChecked(False)  # 기본 접힘
        adv_box.setToolTip(tr('settings.advanced_thresholds_tooltip'))
        adv_layout = QVBoxLayout()
        adv_layout.setSpacing(4)

        # 점심 알림 임계 (출근 후 N시간)
        self.lunch_reminder_spin = self._make_threshold_spin(1, 12, 4, tr('settings.suffix_hour'))
        self.lunch_reminder_spin.setToolTip(tr('settings.lunch_alert_tooltip'))
        adv_layout.addLayout(self._labeled_row(tr('settings.lunch_alert_after'), self.lunch_reminder_spin))

        # 저녁 알림 임계 (출근 후 N시간)
        self.dinner_reminder_spin = self._make_threshold_spin(1, 16, 8, tr('settings.suffix_hour'))
        self.dinner_reminder_spin.setToolTip(tr('settings.dinner_alert_tooltip'))
        adv_layout.addLayout(self._labeled_row(tr('settings.dinner_alert_after'), self.dinner_reminder_spin))

        # 연장근무 누적 임계
        self.overtime_threshold_spin = self._make_threshold_spin(1, 200, 20, tr('settings.suffix_hour'))
        self.overtime_threshold_spin.setToolTip(tr('settings.overtime_alert_tooltip'))
        adv_layout.addLayout(self._labeled_row(tr('settings.overtime_alert_at'), self.overtime_threshold_spin))

        # 주 X시간 임계
        self.weekly_hours_spin = self._make_threshold_spin(20, 168, 52, tr('settings.suffix_hour'))
        self.weekly_hours_spin.setToolTip(tr('settings.weekly_limit_tooltip'))
        adv_layout.addLayout(self._labeled_row(tr('settings.weekly_limit'), self.weekly_hours_spin))

        # 연속 연장근무 일수
        self.health_consecutive_spin = self._make_threshold_spin(1, 14, 3, tr('label.unit_day'))
        self.health_consecutive_spin.setToolTip(tr('settings.consecutive_ot_tooltip', fallback=''))
        adv_layout.addLayout(self._labeled_row(tr('settings.consecutive_ot'), self.health_consecutive_spin))

        # 휴식 권고 (연속 근무 시간)
        self.health_break_hours_spin = self._make_threshold_spin(1, 12, 4, tr('settings.suffix_hour'))
        self.health_break_hours_spin.setToolTip(tr('settings.break_after_tooltip'))
        adv_layout.addLayout(self._labeled_row(tr('settings.break_after'), self.health_break_hours_spin))

        adv_box.setLayout(adv_layout)
        layout.addWidget(adv_box)

        # 시간 형식 + 테마 한 줄에
        format_row = QHBoxLayout()
        time_format_label = QLabel(tr('settings.time_format'))
        time_format_label.setFixedWidth(70)
        self.time_format_combo = QComboBox()
        self.time_format_combo.addItem(tr('settings.time_format_24'), "24")
        self.time_format_combo.addItem(tr('settings.time_format_12'), "12")
        self.time_format_combo.setFixedWidth(180)

        theme_label = QLabel(tr('settings.theme'))
        theme_label.setFixedWidth(40)
        self.theme_combo = QComboBox()
        self.theme_combo.addItem(tr('settings.theme_light'), "light")
        self.theme_combo.addItem(tr('settings.theme_dark'), "dark")
        self.theme_combo.setFixedWidth(90)
        self.theme_combo.currentIndexChanged.connect(self.on_theme_changed)

        format_row.addWidget(time_format_label)
        format_row.addWidget(self.time_format_combo)
        format_row.addSpacing(16)
        format_row.addWidget(theme_label)
        format_row.addWidget(self.theme_combo)
        format_row.addStretch()
        layout.addLayout(format_row)

        # 접근성: 글꼴 크기 + 고대비
        a11y_row = QHBoxLayout()
        a11y_row.addWidget(QLabel(tr('settings.font_scale')))
        self.font_scale_combo = QComboBox()
        self.font_scale_combo.addItem("100%", "1.0")
        self.font_scale_combo.addItem("125%", "1.25")
        self.font_scale_combo.addItem("150%", "1.5")
        self.font_scale_combo.setFixedWidth(90)
        a11y_row.addWidget(self.font_scale_combo)
        a11y_row.addSpacing(16)
        self.high_contrast_check = QCheckBox(tr('settings.high_contrast'))
        self.high_contrast_check.setToolTip(tr('settings.high_contrast_tooltip'))
        a11y_row.addWidget(self.high_contrast_check)
        a11y_row.addStretch()
        layout.addLayout(a11y_row)

        # 언어 선택
        from core.i18n import available_languages, language_label
        lang_row = QHBoxLayout()
        lang_label = QLabel(tr('label.language'))
        lang_label.setFixedWidth(120)
        self.language_combo = QComboBox()
        for code in available_languages():
            self.language_combo.addItem(language_label(code), code)
        self.language_combo.setFixedWidth(140)
        self.language_combo.setToolTip(tr('group.language_restart_tooltip', fallback=''))
        lang_row.addWidget(lang_label)
        lang_row.addWidget(self.language_combo)
        lang_row.addStretch()
        layout.addLayout(lang_row)

        group.setLayout(layout)
        return group

    def create_overtime_group(self) -> QGroupBox:
        """연장근무 설정 그룹"""
        group = QGroupBox(tr('group.overtime'))
        layout = QVBoxLayout()
        layout.setSpacing(6)

        # 잔액 + 계산 단위 한 줄
        top_row = QHBoxLayout()
        self.current_overtime_label = QLabel(tr('settings.current_balance'))
        self.current_overtime_label.setObjectName("badge_success")
        top_row.addWidget(self.current_overtime_label)
        top_row.addStretch()

        unit_label = QLabel(tr('settings.calc_unit'))
        self.overtime_unit_combo = QComboBox()
        self.overtime_unit_combo.addItem(tr('view.overtime.minute_30'), 30)
        self.overtime_unit_combo.addItem(tr('label.time_hours_minutes', hours=1, minutes=0), 60)
        self.overtime_unit_combo.addItem(tr('view.overtime.minute_0'), 15)
        self.overtime_unit_combo.setFixedWidth(100)
        top_row.addWidget(unit_label)
        top_row.addWidget(self.overtime_unit_combo)
        layout.addLayout(top_row)

        # 초기 연장근무 설정
        initial_overtime_layout = QHBoxLayout()
        initial_overtime_label = QLabel(tr('settings.initial_overtime'))
        initial_overtime_label.setFixedWidth(100)
        self.initial_overtime_hours = QSpinBox()
        self.initial_overtime_hours.setRange(0, 200)
        self.initial_overtime_hours.setValue(0)
        self.initial_overtime_hours.setSuffix(tr('settings.suffix_hour'))
        self.initial_overtime_hours.setFixedWidth(110)

        self.initial_overtime_mins = QSpinBox()
        self.initial_overtime_mins.setRange(0, 59)
        self.initial_overtime_mins.setValue(0)
        self.initial_overtime_mins.setSuffix(tr('settings.suffix_minute'))
        self.initial_overtime_mins.setFixedWidth(100)

        apply_overtime_btn = QPushButton(tr('btn.apply'))
        apply_overtime_btn.setObjectName("btn_small")
        apply_overtime_btn.setFixedWidth(50)
        apply_overtime_btn.clicked.connect(self.apply_initial_overtime)

        self.auto_overtime_check = QCheckBox(tr('settings.auto_bank'))
        self.auto_overtime_check.setChecked(True)
        self.auto_overtime_check.setToolTip(tr('settings.auto_bank_tooltip'))

        initial_overtime_layout.addWidget(initial_overtime_label)
        initial_overtime_layout.addWidget(self.initial_overtime_hours)
        initial_overtime_layout.addWidget(self.initial_overtime_mins)
        initial_overtime_layout.addWidget(apply_overtime_btn)
        initial_overtime_layout.addStretch()
        initial_overtime_layout.addWidget(self.auto_overtime_check)
        layout.addLayout(initial_overtime_layout)

        initial_overtime_note = QLabel(tr('settings.initial_overtime_note', fallback=''))
        initial_overtime_note.setObjectName("note_text")
        layout.addWidget(initial_overtime_note)

        group.setLayout(layout)
        return group

    def create_goal_group(self) -> QGroupBox:
        """월간 목표 설정 그룹 (0=비활성)."""
        group = QGroupBox(tr('settings.goal_group'))
        layout = QVBoxLayout()
        layout.setSpacing(6)

        # 연장근무 상한
        ot_row = QHBoxLayout()
        ot_label = QLabel(tr('settings.monthly_ot_cap'))
        ot_label.setFixedWidth(150)
        self.goal_ot_h = QSpinBox()
        self.goal_ot_h.setRange(0, 100)
        self.goal_ot_h.setSuffix(tr('settings.suffix_hour'))
        self.goal_ot_h.setFixedWidth(100)
        self.goal_ot_m = QSpinBox()
        self.goal_ot_m.setRange(0, 59)
        self.goal_ot_m.setSingleStep(30)
        self.goal_ot_m.setSuffix(tr('settings.suffix_minute'))
        self.goal_ot_m.setFixedWidth(90)
        ot_row.addWidget(ot_label)
        ot_row.addWidget(self.goal_ot_h)
        ot_row.addWidget(self.goal_ot_m)
        ot_row.addStretch()
        layout.addLayout(ot_row)

        # 일평균 목표
        avg_row = QHBoxLayout()
        avg_label = QLabel(tr('settings.daily_avg_goal'))
        avg_label.setFixedWidth(150)
        self.goal_avg = QDoubleSpinBox() if False else QSpinBox()  # int*10 방식
        self.goal_avg.setRange(0, 24)
        self.goal_avg.setSuffix(tr('settings.suffix_hour'))
        self.goal_avg.setFixedWidth(100)
        avg_row.addWidget(avg_label)
        avg_row.addWidget(self.goal_avg)
        avg_row.addStretch()
        layout.addLayout(avg_row)

        note = QLabel(tr('settings.goal_note', fallback=''))
        note.setObjectName("note_text")
        layout.addWidget(note)

        group.setLayout(layout)
        return group

    def create_leave_group(self) -> QGroupBox:
        """휴가 설정 그룹"""
        group = QGroupBox(tr('group.leave'))
        layout = QVBoxLayout()
        layout.setSpacing(6)

        # 연차 개수 + 남은 연차 한 줄
        top_row = QHBoxLayout()
        annual_leave_label = QLabel(tr('settings.annual_leave'))
        annual_leave_label.setFixedWidth(70)
        self.annual_leave_days = QSpinBox()
        self.annual_leave_days.setRange(0, 30)
        self.annual_leave_days.setValue(15)
        self.annual_leave_days.setSuffix(tr('label.unit_day'))
        self.annual_leave_days.setFixedWidth(100)
        top_row.addWidget(annual_leave_label)
        top_row.addWidget(self.annual_leave_days)
        top_row.addStretch()

        self.remaining_leave_label = QLabel(tr('settings.remaining_leave'))
        self.remaining_leave_label.setObjectName("badge_leave")
        top_row.addWidget(self.remaining_leave_label)
        layout.addLayout(top_row)

        # 기존 사용 연차 설정
        used_leave_layout = QHBoxLayout()
        used_leave_label = QLabel(tr('settings.used_leave'))
        used_leave_label.setFixedWidth(70)
        self.used_leave_hours = QSpinBox()
        self.used_leave_hours.setRange(0, 200)
        self.used_leave_hours.setValue(0)
        self.used_leave_hours.setSuffix(tr('settings.suffix_hour'))
        self.used_leave_hours.setFixedWidth(110)

        self.used_leave_mins = QSpinBox()
        self.used_leave_mins.setRange(0, 59)
        self.used_leave_mins.setValue(0)
        self.used_leave_mins.setSuffix(tr('settings.suffix_minute'))
        self.used_leave_mins.setSingleStep(30)
        self.used_leave_mins.setFixedWidth(100)

        apply_used_leave_btn = QPushButton(tr('btn.apply'))
        apply_used_leave_btn.setObjectName("btn_small")
        apply_used_leave_btn.setFixedWidth(50)
        apply_used_leave_btn.clicked.connect(self.apply_used_leave)

        used_leave_layout.addWidget(used_leave_label)
        used_leave_layout.addWidget(self.used_leave_hours)
        used_leave_layout.addWidget(self.used_leave_mins)
        used_leave_layout.addWidget(apply_used_leave_btn)
        used_leave_layout.addStretch()
        layout.addLayout(used_leave_layout)

        used_leave_note = QLabel(tr('settings.used_leave_note', fallback=''))
        used_leave_note.setObjectName("note_text")
        layout.addWidget(used_leave_note)

        group.setLayout(layout)
        return group

    def create_holiday_group(self) -> QGroupBox:
        """공휴일 설정 그룹"""
        group = QGroupBox(tr('group.holiday'))
        layout = QVBoxLayout()
        layout.setSpacing(6)

        # 공휴일 목록 + 버튼 한 줄
        button_layout = QHBoxLayout()
        holiday_list_label = QLabel(tr('settings.registered'))
        button_layout.addWidget(holiday_list_label)

        self.holiday_count_label = QLabel(tr('settings.holiday_count', count=0, year=datetime.now().year))
        self.holiday_count_label.setObjectName("info_text")
        button_layout.addWidget(self.holiday_count_label)
        button_layout.addStretch()

        add_korean_btn = QPushButton(tr('settings.add_korean_holidays'))
        add_korean_btn.setObjectName("btn_small")
        add_korean_btn.setToolTip(tr('settings.add_korean_holidays_tooltip'))
        add_korean_btn.clicked.connect(self.add_korean_holidays_auto)
        button_layout.addWidget(add_korean_btn)

        add_custom_btn = QPushButton(tr('btn.add'))
        add_custom_btn.setObjectName("btn_small")
        add_custom_btn.clicked.connect(self.add_custom_holiday)
        button_layout.addWidget(add_custom_btn)

        view_holidays_btn = QPushButton(tr('settings.list'))
        view_holidays_btn.setObjectName("btn_small")
        view_holidays_btn.clicked.connect(self.view_holidays)
        button_layout.addWidget(view_holidays_btn)

        layout.addLayout(button_layout)

        holiday_note = QLabel(tr('settings.holiday_note', fallback=''))
        holiday_note.setObjectName("note_text")
        layout.addWidget(holiday_note)

        group.setLayout(layout)

        # 공휴일 개수 업데이트
        self.update_holiday_count()

        return group

    def update_holiday_count(self):
        """공휴일 개수 표시 업데이트"""
        current_year = datetime.now().year
        holidays = self.db.get_holidays_by_year(current_year)
        self.holiday_count_label.setText(tr('settings.holiday_count', count=len(holidays), year=current_year))

    def add_korean_holidays_auto(self):
        """holidays 패키지로 음력/임시 공휴일 포함 자동 추가.

        11월 이후 호출 시 자동으로 다음 연도까지 등록 — 연말 경계에서
        신정 등이 누락되는 것 방지.
        """
        now = datetime.now()
        current_year = now.year
        # 11~12월에 호출 시 다음 해 1월 신정·설 연휴 미리 등록 (연말 자정 경계 대응)
        include_next = now.month >= 11
        target_label = (tr('settings.korean_holidays_years_label', start=current_year, end=current_year + 1)
                          if include_next else tr('settings.korean_holidays_years_label_single', year=current_year))

        reply = QMessageBox.question(
            self,
            tr('settings.korean_holidays_title'),
            tr('settings.korean_holidays_body', years=target_label),
            QMessageBox.Yes | QMessageBox.No
        )
        if reply != QMessageBox.Yes:
            return

        added = self.db.add_korean_holidays_auto(current_year, include_next_year=include_next)
        if added < 0:
            # 패키지 미설치 시 고정 공휴일로 폴백
            self.db.add_korean_holidays(current_year)
            if include_next:
                self.db.add_korean_holidays(current_year + 1)
            self.update_holiday_count()
            QMessageBox.warning(
                self,
                tr('settings.package_not_installed'),
                tr('settings.package_fallback_body', hint=tr('settings.package_install_hint'))
            )
            return

        self.update_holiday_count()
        QMessageBox.information(
            self,
            tr('settings.add_done'),
            tr('settings.korean_holidays_added', year=current_year, count=added)
        )

    def add_custom_holiday(self):
        """사용자 정의 공휴일 추가"""
        from PyQt5.QtWidgets import QInputDialog, QLineEdit

        # 날짜 입력
        today = datetime.now().date().isoformat()
        date_str, ok = QInputDialog.getText(
            self,
            tr('settings.holiday_add_title'),
            tr('settings.holiday_date_prompt'),
            QLineEdit.Normal,
            today
        )

        if not ok or not date_str:
            return

        # 날짜 형식 검증
        try:
            datetime.strptime(date_str, "%Y-%m-%d")
        except ValueError:
            QMessageBox.warning(
                self,
                tr('msg.input_error.title'),
                tr('msg.input_error.date_format')
            )
            return

        # 공휴일 이름 입력
        name, ok = QInputDialog.getText(
            self,
            tr('settings.holiday_add_title'),
            tr('settings.holiday_name_prompt'),
            QLineEdit.Normal,
            ""
        )

        if not ok or not name:
            return

        # 공휴일 추가
        self.db.add_holiday(date_str, name, is_recurring=False)
        self.update_holiday_count()

        QMessageBox.information(
            self,
            tr('settings.add_done'),
            tr('settings.holiday_added', date=date_str, name=name)
        )

    def view_holidays(self):
        """공휴일 목록 보기"""
        current_year = datetime.now().year
        holidays = self.db.get_holidays_by_year(current_year)

        if not holidays:
            QMessageBox.information(
                self,
                tr('settings.holiday_list_title'),
                tr('stats.no_data')
            )
            return

        # 목록 생성
        holiday_list = tr('settings.holiday_list_header', year=current_year)
        for h in holidays:
            date_obj = datetime.strptime(h['date'], "%Y-%m-%d")
            weekday = tr(f"label.weekday_{['mon','tue','wed','thu','fri','sat','sun'][date_obj.weekday()]}")
            recurring = tr('label.recurring_yearly') if h['is_recurring'] else ""
            holiday_list += tr('settings.holiday_list_item',
                               date=h['date'], weekday=weekday,
                               name=h['name'], recurring=recurring)

        holiday_list += '\n' + tr('settings.holiday_total', count=len(holidays))

        # 삭제 옵션 제공
        reply = QMessageBox.question(
            self,
            tr('settings.holiday_list_title'),
            holiday_list + tr('settings.holiday_delete_confirm'),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel
        )

        if reply == QMessageBox.Yes:
            self.delete_holiday_dialog()

    def delete_holiday_dialog(self):
        """공휴일 삭제 다이얼로그"""
        from PyQt5.QtWidgets import QInputDialog

        current_year = datetime.now().year
        holidays = self.db.get_holidays_by_year(current_year)

        if not holidays:
            return

        # 공휴일 선택
        items = [f"{h['date']}: {h['name']}" for h in holidays]
        item, ok = QInputDialog.getItem(
            self,
            tr('settings.holiday_delete_title'),
            tr('settings.holiday_delete_prompt'),
            items,
            0,
            False
        )

        if ok and item:
            date_str = item.split(":")[0]
            self.db.delete_holiday_by_date(date_str)
            self.update_holiday_count()
            QMessageBox.information(self, tr('settings.delete_done'), tr('settings.holiday_deleted', item=item))

    def create_data_group(self) -> QGroupBox:
        """데이터 관리 그룹"""
        group = QGroupBox(tr('group.data'))
        layout = QVBoxLayout()
        layout.setSpacing(6)

        # CSV 내보내기 버튼들 한 줄
        export_layout = QHBoxLayout()

        export_work_btn = QPushButton(tr('settings.export_work'))
        export_work_btn.setObjectName("btn_small")
        export_work_btn.clicked.connect(self.export_work_records)
        export_layout.addWidget(export_work_btn)

        export_overtime_btn = QPushButton(tr('settings.export_overtime'))
        export_overtime_btn.setObjectName("btn_small")
        export_overtime_btn.clicked.connect(self.export_overtime_summary)
        export_layout.addWidget(export_overtime_btn)

        monthly_btn = QPushButton(tr('settings.export_monthly'))
        monthly_btn.setObjectName("btn_small")
        monthly_btn.clicked.connect(self.export_monthly_summary)
        export_layout.addWidget(monthly_btn)

        export_label = QLabel(tr('settings.export_csv'))
        export_label.setObjectName("note_text")
        export_layout.addWidget(export_label)
        export_layout.addStretch()

        layout.addLayout(export_layout)

        # CSV 가져오기
        import_layout = QHBoxLayout()
        import_btn = QPushButton(tr('settings.import_csv'))
        import_btn.setObjectName("btn_small")
        import_btn.setToolTip(tr('settings.import_tooltip'))
        import_btn.clicked.connect(self._import_csv)
        import_layout.addWidget(import_btn)
        import_label = QLabel(tr('settings.import_format'))
        import_label.setObjectName("note_text")
        import_layout.addWidget(import_label)
        import_layout.addStretch()
        layout.addLayout(import_layout)

        # DB 경로 설정 (클라우드 동기화 가능)
        db_path_layout = QHBoxLayout()
        db_path_label = QLabel(tr('settings.db_path_label'))
        db_path_label.setFixedWidth(60)
        self.db_path_edit = QLineEdit()
        self.db_path_edit.setReadOnly(True)
        self.db_path_edit.setText(self.db.db_path if hasattr(self.db, 'db_path') else 'database.db')
        db_path_btn = QPushButton(tr('settings.change'))
        db_path_btn.setObjectName("btn_small")
        db_path_btn.setToolTip(tr('settings.db_path_tooltip'))
        db_path_btn.clicked.connect(self._change_db_path)
        db_path_layout.addWidget(db_path_label)
        db_path_layout.addWidget(self.db_path_edit, 1)
        db_path_layout.addWidget(db_path_btn)
        layout.addLayout(db_path_layout)

        # 자동 외출 (화면 잠금 시)
        self.auto_break_check = QCheckBox(tr('settings.auto_break_lock'))
        self.auto_break_check.setToolTip(tr('settings.auto_break_lock_tooltip', fallback=''))
        layout.addWidget(self.auto_break_check)

        # Gitea 피드백 토큰 (옵션, crash 자동 보고용)
        feedback_layout = QHBoxLayout()
        feedback_label = QLabel(tr('settings.gitea_feedback_label', fallback=''))
        feedback_label.setFixedWidth(80)
        self.gitea_token_edit = QLineEdit()
        self.gitea_token_edit.setEchoMode(QLineEdit.Password)
        self.gitea_token_edit.setPlaceholderText(tr('settings.gitea_token_placeholder', fallback=''))
        feedback_layout.addWidget(feedback_label)
        feedback_layout.addWidget(self.gitea_token_edit, 1)
        layout.addLayout(feedback_layout)

        self.gitea_feedback_enabled_check = QCheckBox(tr('settings.gitea_feedback'))
        layout.addWidget(self.gitea_feedback_enabled_check)

        # 첫 잠금 해제 = 출근 (PC를 안 끄는 사용자용)
        self.clock_in_unlock_check = QCheckBox(tr('settings.clock_in_unlock'))
        self.clock_in_unlock_check.setToolTip(tr('settings.clock_in_unlock_tooltip', fallback=''))
        layout.addWidget(self.clock_in_unlock_check)

        # 업데이트 확인
        update_layout = QHBoxLayout()
        from core.version import __version__
        version_label = QLabel(tr('settings.version', version=__version__))
        version_label.setObjectName("note_text")
        update_btn = QPushButton(tr('settings.check_update'))
        update_btn.setObjectName("btn_small")
        update_btn.clicked.connect(self._check_updates)
        update_layout.addWidget(version_label)
        update_layout.addStretch()
        update_layout.addWidget(update_btn)
        layout.addLayout(update_layout)

        group.setLayout(layout)
        return group

    def _change_db_path(self):
        """DB 경로 변경 (재시작 후 적용)"""
        current = self.db.db_path if hasattr(self.db, 'db_path') else 'database.db'
        new_path, _ = QFileDialog.getSaveFileName(
            self,
            tr('settings.select_db'),
            current,
            "SQLite Database (*.db)"
        )
        if not new_path:
            return
        # 파일 미존재 시 빈 파일 생성하지 않고, 경로만 저장 — 다음 실행 시 새 DB로 init
        self.db.set_setting(DB_PATH_OVERRIDE, new_path)
        self.db_path_edit.setText(new_path)
        QMessageBox.information(
            self,
            tr('settings.db_path_label')[:-1],
            tr('settings.db_path_saved', path=new_path)
        )

    def load_settings(self):
        """설정 불러오기"""
        settings = self.db.get_settings()

        if settings:
            # work_minutes 우선, 없으면 work_hours*60 폴백
            work_minutes = settings.get(WORK_MINUTES)
            if work_minutes is None:
                try:
                    work_minutes = int(round(float(settings.get(WORK_HOURS, 8)) * 60))
                except (ValueError, TypeError):
                    work_minutes = 480
            work_minutes = int(work_minutes)

            # blockSignals: load 시 _on_work_time_user_edit가 프리셋을 잘못 전환하지 않도록
            self.work_hours_spin.blockSignals(True)
            self.work_minutes_spin.blockSignals(True)
            try:
                self.work_hours_spin.setValue(work_minutes // 60)
                self.work_minutes_spin.setValue(work_minutes % 60)
            finally:
                self.work_hours_spin.blockSignals(False)
                self.work_minutes_spin.blockSignals(False)

            lunch_minutes = int(settings.get(LUNCH_DURATION_MINUTES, 60))
            self.lunch_spin.blockSignals(True)
            try:
                self.lunch_spin.setValue(lunch_minutes)
            finally:
                self.lunch_spin.blockSignals(False)

            # 현재 (work_minutes, lunch_minutes)와 일치하는 프리셋 선택
            current = (work_minutes, lunch_minutes)
            preset_idx = self.work_preset_combo.count() - 1  # 기본값: 사용자 정의
            for i in range(self.work_preset_combo.count()):
                if self.work_preset_combo.itemData(i) == current:
                    preset_idx = i
                    break
            self.work_preset_combo.blockSignals(True)
            self.work_preset_combo.setCurrentIndex(preset_idx)
            self.work_preset_combo.blockSignals(False)

            self.auto_lunch_check.setChecked(settings.get(AUTO_LUNCH, False))

            self.dinner_spin.setValue(int(settings.get(DINNER_DURATION_MINUTES, 60)))

            # 자동 외출 (화면 잠금)
            if hasattr(self, 'auto_break_check'):
                self.auto_break_check.setChecked(settings.get(AUTO_BREAK_ON_LOCK, False))
            if hasattr(self, 'clock_in_unlock_check'):
                self.clock_in_unlock_check.setChecked(settings.get(CLOCK_IN_ON_UNLOCK, False))

            # 목표
            if hasattr(self, 'goal_ot_h'):
                ot_min = int(settings.get(GOAL_OVERTIME_MAX_MONTHLY, 0) or 0)
                self.goal_ot_h.setValue(ot_min // 60)
                self.goal_ot_m.setValue(ot_min % 60)
            if hasattr(self, 'goal_avg'):
                self.goal_avg.setValue(int(float(settings.get(GOAL_AVG_HOURS_DAILY, 0) or 0)))

            # Gitea 피드백
            if hasattr(self, 'gitea_token_edit'):
                self.gitea_token_edit.setText(self.db.get_setting(GITEA_FEEDBACK_TOKEN, '') or '')
            if hasattr(self, 'gitea_feedback_enabled_check'):
                self.gitea_feedback_enabled_check.setChecked(
                    settings.get(GITEA_FEEDBACK_ENABLED, False)
                )

            # 접근성
            if hasattr(self, 'font_scale_combo'):
                scale = str(settings.get(FONT_SCALE, '1.0'))
                idx = self.font_scale_combo.findData(scale)
                if idx >= 0:
                    self.font_scale_combo.setCurrentIndex(idx)
            if hasattr(self, 'high_contrast_check'):
                self.high_contrast_check.setChecked(settings.get(HIGH_CONTRAST, False))

            # 알림
            self.clock_out_notification_check.setChecked(settings.get(NOTIF_CLOCK_OUT, True))
            self.lunch_notification_check.setChecked(settings.get(NOTIF_LUNCH, True))
            if hasattr(self, 'dinner_notification_check'):
                self.dinner_notification_check.setChecked(settings.get(NOTIF_DINNER, True))
            self.overtime_notification_check.setChecked(settings.get(NOTIF_OVERTIME, True))
            self.health_notification_check.setChecked(settings.get(NOTIF_HEALTH, True))
            if hasattr(self, 'health_break_notification_check'):
                self.health_break_notification_check.setChecked(
                    settings.get(HEALTH_BREAK_ENABLED, True))
            if hasattr(self, 'notif_before_spin'):
                try:
                    self.notif_before_spin.setValue(int(settings.get(NOTIFICATION_BEFORE_MINUTES, 30)))
                except (ValueError, TypeError):
                    self.notif_before_spin.setValue(30)

            # 고급 임계값
            self._load_threshold_safely(settings, LUNCH_REMINDER_HOURS, 'lunch_reminder_spin', 4)
            self._load_threshold_safely(settings, DINNER_REMINDER_HOURS, 'dinner_reminder_spin', 8)
            self._load_threshold_safely(settings, OVERTIME_THRESHOLD_HOURS, 'overtime_threshold_spin', 20)
            self._load_threshold_safely(settings, WEEKLY_HOURS_THRESHOLD, 'weekly_hours_spin', 52)
            self._load_threshold_safely(settings, HEALTH_CONSECUTIVE_OT_DAYS, 'health_consecutive_spin', 3)
            self._load_threshold_safely(settings, HEALTH_BREAK_HOURS, 'health_break_hours_spin', 4)

            # 시간 형식 (콤보박스는 문자열로 저장하므로 변환)
            time_format = settings.get(TIME_FORMAT, '24')
            if isinstance(time_format, int):
                time_format = str(time_format)
            index = self.time_format_combo.findData(time_format)
            if index >= 0:
                self.time_format_combo.setCurrentIndex(index)

            # 테마
            self.theme_combo.setCurrentIndex(0 if settings.get(THEME, 'dark') == 'light' else 1)

            # 언어 선택 적용
            if hasattr(self, 'language_combo'):
                lang = settings.get(LANGUAGE, 'ko') or 'ko'
                idx = self.language_combo.findData(lang)
                if idx >= 0:
                    self.language_combo.setCurrentIndex(idx)

            # 연장근무 (콤보박스는 정수로 저장)
            overtime_unit = settings.get(OVERTIME_UNIT, 30)
            if isinstance(overtime_unit, str):
                overtime_unit = int(overtime_unit)
            index = self.overtime_unit_combo.findData(overtime_unit)
            if index >= 0:
                self.overtime_unit_combo.setCurrentIndex(index)
            self.auto_overtime_check.setChecked(settings.get(AUTO_OVERTIME, True))

            # 휴가
            self.annual_leave_days.setValue(settings.get(ANNUAL_LEAVE_DAYS, 15))

        # 기존 연장근무 초기값 로드 (settings에서)
        initial_overtime = int(self.db.get_setting(INITIAL_OVERTIME_MINUTES, '0'))
        self.initial_overtime_hours.setValue(initial_overtime // 60)
        self.initial_overtime_mins.setValue(initial_overtime % 60)

        # 기존 사용 연차 초기값 로드 (settings에서)
        initial_leave_hours = float(self.db.get_setting(INITIAL_LEAVE_USED_HOURS, '0'))
        self.used_leave_hours.setValue(int(initial_leave_hours))
        self.used_leave_mins.setValue(int((initial_leave_hours % 1) * 60))

        # 남은 연차 계산
        self.update_remaining_leave()

    def _import_csv(self):
        """CSV 파일에서 근무 기록 일괄 가져오기."""
        path, _ = QFileDialog.getOpenFileName(
            self, tr('settings.import_csv'),
            os.path.expanduser("~"),
            "CSV files (*.csv);;All files (*.*)",
        )
        if not path:
            return
        try:
            from utils.csv_importer import parse_csv, import_records
            rows = parse_csv(path)
        except (FileNotFoundError, ValueError) as e:
            QMessageBox.critical(self, tr('settings.parse_failed'), str(e))
            return

        if not rows:
            QMessageBox.information(self, tr('settings.empty_file'), tr('settings.empty_file_body'))
            return

        reply = QMessageBox.question(
            self,
            tr('settings.conflict_title'),
            tr('settings.import_rows_intro', count=len(rows)) + tr('settings.conflict_body_detailed'),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
        )
        if reply == QMessageBox.Cancel:
            return
        policy = 'overwrite' if reply == QMessageBox.Yes else 'skip'

        try:
            added, updated, skipped = import_records(self.db, rows, on_conflict=policy)
        except Exception as e:
            QMessageBox.critical(self, tr('settings.import_failed'), str(e))
            return

        QMessageBox.information(
            self, tr('settings.import_complete'),
            tr('settings.import_result', added=added, updated=updated, skipped=skipped)
        )

    def _check_updates(self):
        """설정 창에서 업데이트 확인 트리거 → 부모 윈도우로 위임."""
        if self.parent_window and hasattr(self.parent_window, 'check_for_updates'):
            self.parent_window.check_for_updates(silent=False)

    def _restart_app(self):
        """언어 변경 후 자동 재시작 (사용자 명시 동의 시)."""
        from PyQt5.QtWidgets import QApplication
        import sys, os
        QApplication.quit()
        # 빌드된 exe / 개발 환경 모두 처리
        if getattr(sys, 'frozen', False):
            os.execv(sys.executable, [sys.executable])
        else:
            os.execv(sys.executable, [sys.executable, *sys.argv])

    def save_settings(self):
        """설정 저장"""
        # 점심시간은 분 단위 그대로 저장
        lunch_minutes = self.lunch_spin.value()
        # 저녁시간은 분 단위 그대로 저장
        dinner_minutes = self.dinner_spin.value()

        work_minutes_total = self.work_hours_spin.value() * 60 + self.work_minutes_spin.value()
        if work_minutes_total < 30:
            QMessageBox.warning(self, tr('msg.input_error.title'),
                                tr('msg.work_min_too_small'))
            return

        # work_hours는 db.save_settings()가 자동 동기화하므로 보내지 않음 (단일 진실 소스)
        settings = {
            WORK_MINUTES: work_minutes_total,
            LUNCH_DURATION_MINUTES: lunch_minutes,
            DINNER_DURATION_MINUTES: dinner_minutes,
            AUTO_LUNCH: self.auto_lunch_check.isChecked(),
            NOTIF_CLOCK_OUT: self.clock_out_notification_check.isChecked(),
            NOTIF_LUNCH: self.lunch_notification_check.isChecked(),
            NOTIF_OVERTIME: self.overtime_notification_check.isChecked(),
            NOTIF_HEALTH: self.health_notification_check.isChecked(),
            NOTIFICATION_BEFORE_MINUTES: self.notif_before_spin.value(),
            # 고급 임계값
            LUNCH_REMINDER_HOURS: self.lunch_reminder_spin.value(),
            DINNER_REMINDER_HOURS: self.dinner_reminder_spin.value(),
            OVERTIME_THRESHOLD_HOURS: self.overtime_threshold_spin.value(),
            WEEKLY_HOURS_THRESHOLD: self.weekly_hours_spin.value(),
            HEALTH_CONSECUTIVE_OT_DAYS: self.health_consecutive_spin.value(),
            HEALTH_BREAK_HOURS: self.health_break_hours_spin.value(),
            TIME_FORMAT: self.time_format_combo.currentData(),
            OVERTIME_UNIT: self.overtime_unit_combo.currentData(),
            AUTO_OVERTIME: self.auto_overtime_check.isChecked(),
            ANNUAL_LEAVE_DAYS: self.annual_leave_days.value(),
        }
        if hasattr(self, 'dinner_notification_check'):
            settings[NOTIF_DINNER] = self.dinner_notification_check.isChecked()
        if hasattr(self, 'health_break_notification_check'):
            settings[HEALTH_BREAK_ENABLED] = self.health_break_notification_check.isChecked()
        if hasattr(self, 'auto_break_check'):
            settings[AUTO_BREAK_ON_LOCK] = self.auto_break_check.isChecked()
        if hasattr(self, 'clock_in_unlock_check'):
            settings[CLOCK_IN_ON_UNLOCK] = self.clock_in_unlock_check.isChecked()
        if hasattr(self, 'goal_ot_h'):
            settings[GOAL_OVERTIME_MAX_MONTHLY] = self.goal_ot_h.value() * 60 + self.goal_ot_m.value()
        if hasattr(self, 'goal_avg'):
            settings[GOAL_AVG_HOURS_DAILY] = self.goal_avg.value()
        if hasattr(self, 'gitea_token_edit'):
            self.db.set_setting(GITEA_FEEDBACK_TOKEN, self.gitea_token_edit.text().strip())
        if hasattr(self, 'gitea_feedback_enabled_check'):
            settings[GITEA_FEEDBACK_ENABLED] = self.gitea_feedback_enabled_check.isChecked()
        if hasattr(self, 'font_scale_combo'):
            settings[FONT_SCALE] = self.font_scale_combo.currentData()
        if hasattr(self, 'high_contrast_check'):
            settings[HIGH_CONTRAST] = self.high_contrast_check.isChecked()
        if hasattr(self, 'language_combo'):
            settings[LANGUAGE] = self.language_combo.currentData()

        self.db.save_settings(settings)

        # 테마 저장
        self.db.set_setting(THEME, self.theme_combo.currentData())

        # 연차 잔액 재계산 (총 연차 - 올해 사용한 연차)
        current_year = datetime.now().year
        all_leaves = self.db.get_all_leave_records(limit=365)
        year_leaves = [r for r in all_leaves if r['date'].startswith(str(current_year))]
        used_annual_days = sum(r['days'] for r in year_leaves)
        new_balance = settings[ANNUAL_LEAVE_DAYS] - used_annual_days
        self.db.set_leave_balance(new_balance)

        QMessageBox.information(
            self,
            tr('settings.save_done'),
            tr('settings.save_done_body')
        )

        # 부모 윈도우에 설정 변경 알림
        if self.parent_window and hasattr(self.parent_window, 'reload_settings'):
            self.parent_window.reload_settings()

        # 언어 변경 감지 → 등록된 위젯 즉시 재번역, 아직 미등록 영역은 재시작 권장
        if hasattr(self, 'language_combo'):
            from core.i18n import get_language
            from ui.i18n_runtime import set_language_and_retranslate
            new_lang = self.language_combo.currentData()
            if new_lang and new_lang != get_language():
                set_language_and_retranslate(new_lang)
                reply = QMessageBox.question(
                    self,
                    tr('settings.restart_title'),
                    tr('settings.restart_body'),
                    QMessageBox.Yes | QMessageBox.No,
                )
                if reply == QMessageBox.Yes:
                    self._restart_app()

    def on_theme_changed(self, index):
        """테마 콤보박스 변경 시 즉시 적용"""
        if not hasattr(self, '_settings_loaded'):
            return
        theme_name = self.theme_combo.currentData()
        if theme_name:
            # DB에 저장
            self.db.set_setting(THEME, theme_name)
            # 부모 윈도우를 통해 테마 적용 (setStyleSheet이 메인 윈도우에 설정되므로)
            if self.parent_window and hasattr(self.parent_window, 'apply_theme'):
                self.parent_window.apply_theme(theme_name)

    def apply_initial_overtime(self):
        """기존 연장근무 설정 (프로그램 사용 전 쌓인 시간 - 절대값)"""
        try:
            hours = self.initial_overtime_hours.value()
            mins = self.initial_overtime_mins.value()
            new_initial_minutes = hours * 60 + mins

            # 기존 초기값 조회
            old_initial_minutes = int(self.db.get_setting(INITIAL_OVERTIME_MINUTES, '0'))
            old_hours = old_initial_minutes // 60
            old_mins = old_initial_minutes % 60

            reply = QMessageBox.question(
                self,
                tr('settings.initial_overtime_title'),
                tr('settings.initial_overtime_body', old_hours=old_hours, old_mins=old_mins, hours=hours, mins=mins),
                QMessageBox.Yes | QMessageBox.No
            )

            if reply == QMessageBox.Yes:
                # settings에 초기값 저장
                self.db.set_setting(INITIAL_OVERTIME_MINUTES,str(new_initial_minutes))

                QMessageBox.information(
                    self,
                    tr('settings.initial_overtime_done'),
                    tr('settings.initial_overtime_done_body', hours=hours, mins=mins)
                )

                # 부모 윈도우 잔액 업데이트
                if self.parent_window and hasattr(self.parent_window, 'update_overtime_balance'):
                    self.parent_window.update_overtime_balance()

                # 현재 창의 잔액 표시도 업데이트
                self.update_overtime_balance_display()
        except Exception as e:
            QMessageBox.critical(
                self,
                tr('settings.error'),
                tr('settings.initial_overtime_error', error=str(e))
            )

    def update_overtime_balance_display(self):
        """연장근무 잔액 표시 업데이트"""
        balance_minutes = self.db.get_total_overtime_balance()
        hours = balance_minutes // 60
        minutes = balance_minutes % 60
        self.current_overtime_label.setText(tr('settings.current_overtime_balance', hours=hours, minutes=minutes, balance=balance_minutes))

    def update_remaining_leave(self):
        """남은 연차 계산 및 표시"""
        # 기존 사용 연차 초기값 (프로그램 사용 전)
        initial_leave_hours = float(self.db.get_setting(INITIAL_LEAVE_USED_HOURS, '0'))
        initial_leave_days = initial_leave_hours / 8.0

        # 올해 사용한 연차 조회 (프로그램에서 기록된 것)
        current_year = datetime.now().year
        all_leaves = self.db.get_all_leave_records(limit=365)
        year_leaves = [r for r in all_leaves if r['date'].startswith(str(current_year))]

        # 모든 연차 타입 합산 (days 필드 사용)
        used_annual_days = sum(r['days'] for r in year_leaves)

        # 총 사용량 = 초기값 + 프로그램에서 기록된 것
        total_used = initial_leave_days + used_annual_days

        # 총 연차
        total_annual = self.annual_leave_days.value()

        # 남은 연차
        remaining = total_annual - total_used

        self.remaining_leave_label.setText(
            tr('settings.remaining_leave_fmt', remaining=remaining, total=total_annual, used=total_used)
        )

    def export_work_records(self):
        """근무 기록 내보내기"""
        # 내보낼 기록 가져오기
        now = datetime.now()
        stats = self.db.get_monthly_stats(now.year, now.month)
        records = stats.get('records', [])

        if not records:
            QMessageBox.warning(self, tr('settings.export_failed'), tr('settings.export_no_records'))
            return

        # 파일 경로 선택
        default_filename = f"work_records_{now.year}{now.month:02d}.csv"
        filename, _ = QFileDialog.getSaveFileName(
            self,
            tr('settings.save_work_title'),
            default_filename,
            "CSV Files (*.csv)"
        )

        if filename:
            try:
                saved_path = CSVExporter.export_work_records(records, filename, db=self.db)
                QMessageBox.information(
                    self,
                    tr('settings.export_done'),
                    tr('settings.work_exported', path=saved_path)
                )
            except Exception as e:
                QMessageBox.critical(self, tr('settings.export_failed'), tr('settings.export_error', error=str(e)))

    def export_overtime_summary(self):
        """연장근무 내역 내보내기"""
        filename, _ = QFileDialog.getSaveFileName(
            self,
            tr('settings.save_ot_title'),
            f"overtime_summary_{datetime.now().strftime('%Y%m%d')}.csv",
            "CSV Files (*.csv)"
        )

        if filename:
            try:
                saved_path = CSVExporter.export_overtime_summary(self.db, filename)
                QMessageBox.information(
                    self,
                    tr('settings.export_done'),
                    tr('settings.ot_exported', path=saved_path)
                )
            except Exception as e:
                QMessageBox.critical(self, tr('settings.export_failed'), tr('settings.export_error', error=str(e)))

    def export_monthly_summary(self):
        """월간 요약 내보내기"""
        now = datetime.now()
        filename, _ = QFileDialog.getSaveFileName(
            self,
            tr('settings.save_monthly_title'),
            f"monthly_summary_{now.year}{now.month:02d}.csv",
            "CSV Files (*.csv)"
        )

        if filename:
            try:
                saved_path = CSVExporter.export_monthly_summary(self.db, now.year, now.month, filename)
                QMessageBox.information(
                    self,
                    tr('settings.export_done'),
                    tr('settings.monthly_exported', path=saved_path)
                )
            except Exception as e:
                QMessageBox.critical(self, tr('settings.export_failed'), tr('settings.export_error', error=str(e)))

    def apply_used_leave(self):
        """기존 사용 연차 설정 (프로그램 사용 전 이미 사용한 연차 - 절대값)"""
        hours = self.used_leave_hours.value()
        mins = self.used_leave_mins.value()
        new_initial_hours = hours + (mins / 60.0)

        # 기존 초기값 조회
        old_initial_hours = float(self.db.get_setting(INITIAL_LEAVE_USED_HOURS, '0'))
        old_hours = int(old_initial_hours)
        old_mins = int((old_initial_hours % 1) * 60)

        reply = QMessageBox.question(
            self,
            tr('settings.initial_leave_title'),
            tr('settings.initial_leave_body', old_hours=old_hours, old_mins=old_mins, hours=hours, mins=mins),
            QMessageBox.Yes | QMessageBox.No
        )

        if reply == QMessageBox.Yes:
            # settings에 초기값 저장
            self.db.set_setting(INITIAL_LEAVE_USED_HOURS,str(new_initial_hours))

            QMessageBox.information(
                self,
                tr('settings.initial_leave_done'),
                tr('settings.initial_leave_done_body', hours=hours, mins=mins)
            )

            # 남은 연차 재계산
            self.update_remaining_leave()

            # 부모 윈도우의 연차 잔액도 업데이트
            if self.parent_window and hasattr(self.parent_window, 'update_leave_balance'):
                self.parent_window.update_leave_balance()

    def add_past_leave_usage(self):
        """이전 사용 연차 기록하기"""
        dialog = AddLeaveDialog(self, self.db)
        if dialog.exec_() == QDialog.Accepted:
            # 남은 연차 재계산
            self.update_remaining_leave()

            # 부모 윈도우의 연차 잔액도 업데이트
            if self.parent_window and hasattr(self.parent_window, 'update_leave_balance'):
                self.parent_window.update_leave_balance()


# 테스트 코드
if __name__ == "__main__":
    from PyQt5.QtWidgets import QApplication

    app = QApplication(sys.argv)
    dialog = SettingsView()
    dialog.exec_()
