from __future__ import annotations
from typing import Optional
from pathlib import Path
import shutil

from PySide6.QtCore import Qt, QTimer, QDate, QUrl
from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTextEdit,
    QFormLayout, QTabWidget, QPushButton, QMessageBox, QFileDialog,
    QAbstractItemView, QTableWidget, QTableWidgetItem, QHeaderView, QSizePolicy,
    QDateEdit, QDoubleSpinBox, QDialog, QCheckBox, QComboBox
)

from PySide6.QtGui import QDesktopServices, QFontDatabase, QFont

from mi100.repository import Repo
from mi100.ui.delegates import RowTintDelegate
from mi100.ui.client_documents import ClientDocumentsTab
from mi100.ui.report_dialog import ReportDialog
from mi100.ui.report_history import ReportHistoryTab
from mi100.ui.email_history_tab import EmailHistoryTab
from mi100.paths import get_app_root
from mi100.services.pdf_documents import sanitize_filename_part
from mi100.services.project_folders import ensure_project_folder, open_folder


CURRENCY_FONT_CSS = 'font-family:"Cambria Math","Segoe UI Symbol","Segoe UI Emoji","DejaVu Sans","Arial Unicode MS","Segoe UI";'


def _best_currency_font(point_size: int | None = None, bold: bool = False) -> QFont:
    db = QFontDatabase()
    for fam in ("Cambria Math", "Segoe UI Symbol", "Segoe UI Emoji", "DejaVu Sans", "Arial Unicode MS", "Segoe UI"):
        if fam in db.families():
            f = QFont(fam)
            if point_size is not None:
                f.setPointSize(point_size)
            f.setBold(bold)
            return f
    f = QFont()
    if point_size is not None:
        f.setPointSize(point_size)
    f.setBold(bold)
    return f

class ClientDetail(QWidget):
    def __init__(self, repo: Repo, client_id: Optional[int], on_deleted=None, on_updated=None):
        super().__init__()
        self.repo = repo
        self.client_id = client_id
        self.on_deleted = on_deleted
        self.on_updated = on_updated

        self._save_timer = QTimer(self)
        self._save_timer.setSingleShot(True)
        self._save_timer.setInterval(300)
        self._save_timer.timeout.connect(self._flush_pending)

        self._pending: dict[str, object] = {}
        self._has_user_input = False

        self._build()
        self._load()

    def _build(self) -> None:
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(10)

        self.title = QLabel("")
        self.title.hide()

        self.delete_btn = QPushButton("Видалити замовника")
        self.delete_btn.setToolTip("Видалити замовника")
        self.delete_btn.setObjectName("Danger")
        self.delete_btn.setMinimumHeight(22)
        self.delete_btn.setStyleSheet("QPushButton{background:#FFA8A8; color:#7C2D12; border:1px solid #E59494; border-radius:6px; padding:2px 10px; font-weight:600;} QPushButton:hover{background:#E59494;}")
        self.delete_btn.clicked.connect(self._delete_client)

        self.tabs = QTabWidget()
        layout.addWidget(self.tabs, 1)

        info = QWidget()
        info_layout = QVBoxLayout(info)
        info_layout.setContentsMargins(0, 0, 0, 0)
        info_layout.setSpacing(12)
        info_layout.addSpacing(10)

        form = QFormLayout()
        form.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
        form.setFormAlignment(Qt.AlignTop)
        form.setVerticalSpacing(12)
        form.setHorizontalSpacing(14)
        form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)

        self.display_name = QLineEdit(); self.display_name.hide()
        self.company_name = QLineEdit(); self.company_name.setMinimumHeight(28); self.company_name.setMinimumWidth(160); self.company_name.setMaximumWidth(160)
        self.responsible = QLineEdit(); self.responsible.setMinimumHeight(28); self.responsible.setMinimumWidth(160); self.responsible.setMaximumWidth(160)
        self.phone = QLineEdit(); self.phone.setMinimumHeight(28); self.phone.setMinimumWidth(160); self.phone.setMaximumWidth(160)
        self.email = QLineEdit(); self.email.setMinimumHeight(28); self.email.setMinimumWidth(160); self.email.setMaximumWidth(160)

        form.addRow(self._info_label("Компанія"), self.company_name)
        form.addRow(self._info_label("Контактна особа"), self.responsible)
        form.addRow(self._info_label("Телефон"), self.phone)
        form.addRow(self._info_label("Email"), self.email)

        top_info = QHBoxLayout(); top_info.setContentsMargins(0, 0, 0, 0); top_info.setSpacing(18)
        left_info = QVBoxLayout(); left_info.setContentsMargins(0, 0, 0, 0); left_info.setSpacing(12); left_info.addLayout(form)

        self.requisites_text = QTextEdit(); self.requisites_text.setMinimumHeight(150); self.requisites_text.setMinimumWidth(300); self.requisites_text.setMaximumWidth(300); self.requisites_text.setStyleSheet("background:#EBEBEB; padding-top:4px;"); self.requisites_text.setPlaceholderText("Реквізити замовника (одним текстом)")
        self.legal_block_text = QTextEdit(); self.legal_block_text.hide()
        self.auth_name = QLineEdit(); self.auth_name.hide()
        self.auth_pos = QLineEdit(); self.auth_pos.hide()
        self.contract_number = QLineEdit(); self.contract_number.hide()
        self.contract_date = QLineEdit(); self.contract_date.hide()

        extra_form = QFormLayout(); extra_form.setLabelAlignment(Qt.AlignRight | Qt.AlignTop); extra_form.setFormAlignment(Qt.AlignTop); extra_form.setVerticalSpacing(12); extra_form.setHorizontalSpacing(14); extra_form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        extra_form.addRow(self._info_label("Реквізити"), self.requisites_text)
        left_info.addLayout(extra_form)

        contract_wrap = QWidget()
        contract_wrap.setStyleSheet("background:transparent; border:none;")
        contract_wrap_l = QVBoxLayout(contract_wrap)
        contract_wrap_l.setContentsMargins(0, 0, 0, 0)
        contract_wrap_l.setSpacing(6)
        self.contract_label = self._info_label('Договір')

        self.notes = QTextEdit(); self.notes.setMinimumHeight(150); self.notes.setMinimumWidth(300); self.notes.setMaximumWidth(300); self.notes.setStyleSheet("background:#EBEBEB; padding-top:4px;"); self.notes.setPlaceholderText("Коментар")

        self.contracts_dir = get_app_root() / 'documents' / 'contracts'
        self.contracts_dir.mkdir(parents=True, exist_ok=True)
        contract_buttons = QHBoxLayout()
        contract_buttons.setContentsMargins(0, 0, 0, 0)
        contract_buttons.setSpacing(8)
        self.btn_add_contract = QPushButton('Додати договір')
        self.btn_add_contract.setMinimumHeight(22)
        self.btn_add_contract.setStyleSheet("QPushButton{background:#4FAF7A; color:#FFFFFF; border:1px solid #459E6E; border-radius:6px; padding:2px 10px; font-weight:600;} QPushButton:hover{background:#459E6E;} QPushButton:disabled{background:#BBF7D0; color:white; border:1px solid #86EFAC;}")
        self.btn_add_contract.clicked.connect(self._add_contract)
        self.btn_open_contract = QPushButton('Відкрити договір')
        self.btn_open_contract.setMinimumHeight(22)
        self.btn_open_contract.setStyleSheet("QPushButton{background:#FFD8A8; color:#8E6129; border:1px solid #E5BF90; border-radius:6px; padding:2px 10px; font-weight:600;} QPushButton:hover{background:#E5BF90;} QPushButton:disabled{background:#EBEBEB; color:#9CA3AF; border:1px solid #D7D7D7;}")
        self.btn_open_contract.clicked.connect(self._open_contract)
        self.btn_delete_contract = QPushButton('Видалити договір')
        self.btn_delete_contract.setMinimumHeight(22)
        self.btn_delete_contract.setStyleSheet("QPushButton{background:#FFA8A8; color:#7C2D12; border:1px solid #E59494; border-radius:6px; padding:2px 10px; font-weight:600;} QPushButton:hover{background:#E59494;} QPushButton:disabled{background:#EBEBEB; color:#9CA3AF; border:1px solid #D7D7D7;}")
        self.btn_delete_contract.clicked.connect(self._delete_contract)
        contract_buttons.addWidget(self.btn_add_contract)
        contract_buttons.addWidget(self.btn_open_contract)
        contract_buttons.addWidget(self.btn_delete_contract)
        contract_buttons.addStretch(1)
        contract_wrap_l.addLayout(contract_buttons)
        extra_form.addRow(self._info_label('Коментар'), self.notes)
        contract_row = QHBoxLayout(); contract_row.setContentsMargins(0, 0, 0, 0); contract_row.setSpacing(14)
        contract_row.addWidget(self.contract_label, 0, Qt.AlignTop)
        contract_row.addWidget(contract_wrap, 0, Qt.AlignLeft | Qt.AlignTop)
        contract_row.addStretch(1)
        left_info.addLayout(contract_row)
        top_info.addLayout(left_info, 0)
        info_layout.addLayout(top_info)
        info_layout.addStretch(1)
        info_delete_row = QHBoxLayout(); info_delete_row.setContentsMargins(0, 4, 0, 0); info_delete_row.addWidget(self.delete_btn, 0, Qt.AlignLeft | Qt.AlignBottom); info_delete_row.addStretch(1); info_layout.addLayout(info_delete_row)

        self.projects_tab = QWidget()
        p_l = QVBoxLayout(self.projects_tab)
        p_l.setContentsMargins(0, 0, 0, 0)
        p_l.setSpacing(8)
        top_projects = QHBoxLayout()
        self.projects_balance_label = QLabel("Баланс:\n₴ 0.00")
        self.projects_balance_label.setStyleSheet("font-weight:700; font-size:11pt; color:#111827; background:transparent; border:none;"); self.projects_balance_label.setTextFormat(Qt.PlainText); self.projects_balance_label.setFont(_best_currency_font(11, True))
        top_projects.addWidget(self.projects_balance_label, 0, Qt.AlignLeft)
        top_projects.addSpacing(16)
        self.projects_debt_label = QLabel("Борг:\n₴ 0.00")
        self.projects_debt_label.setStyleSheet("font-weight:700; font-size:11pt; color:#111827; background:transparent; border:none;"); self.projects_debt_label.setTextFormat(Qt.PlainText); self.projects_debt_label.setFont(_best_currency_font(11, True))
        top_projects.addWidget(self.projects_debt_label, 0, Qt.AlignLeft)
        top_projects.addStretch(1)
        self.btn_pay_all = QPushButton("Оплатити всі")
        self.btn_pay_all.setMinimumHeight(22)
        self.btn_pay_all.clicked.connect(self._pay_all_projects)
        top_projects.addWidget(self.btn_pay_all, 0, Qt.AlignRight)
        p_l.addLayout(top_projects)

        self.projects_table = QTableWidget(0, 10)
        self.projects_table.setHorizontalHeaderLabels(["№", "Дата", "Замовник", "Проєкт", "Підрядники", "Сума", "Статус", "Оплата", "Папка проєкту", "Видалити"])
        self.projects_table.verticalHeader().setVisible(False)
        self.projects_table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.projects_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.projects_table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.projects_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.projects_table.setWordWrap(True)
        self.projects_table.setMouseTracking(True)
        self.projects_table.horizontalHeader().setMinimumSectionSize(56)
        self.projects_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        for idx, mode in [(0,QHeaderView.ResizeToContents),(1,QHeaderView.ResizeToContents),(2,QHeaderView.ResizeToContents),(3,QHeaderView.Stretch),(4,QHeaderView.Stretch),(5,QHeaderView.ResizeToContents),(6,QHeaderView.ResizeToContents),(7,QHeaderView.ResizeToContents),(8,QHeaderView.ResizeToContents),(9,QHeaderView.ResizeToContents)]:
            self.projects_table.horizontalHeader().setSectionResizeMode(idx, mode)
        self._projects_delegate = RowTintDelegate(self.projects_table)
        self.projects_table.setItemDelegate(self._projects_delegate)
        self.projects_table.cellEntered.connect(self._on_projects_hover)
        p_l.addWidget(self.projects_table)

        self.payments_tab = QWidget()
        pay_l = QVBoxLayout(self.payments_tab)
        pay_l.setContentsMargins(0, 0, 0, 0)
        pay_l.setSpacing(8)
        pay_top = QHBoxLayout()
        self.payments_balance_label = QLabel("Баланс:\n₴ 0.00")
        self.payments_balance_label.setStyleSheet(f"font-weight:700; font-size:11pt; color:#111827; background:transparent; border:none; {CURRENCY_FONT_CSS}"); self.payments_balance_label.setTextFormat(Qt.PlainText); self.payments_balance_label.setFont(_best_currency_font(11, True))
        pay_top.addWidget(self.payments_balance_label)
        pay_top.addStretch(1)
        pay_l.addLayout(pay_top)

        add_pay = QHBoxLayout()
        self.payment_date = QDateEdit(); self.payment_date.setCalendarPopup(True); self.payment_date.setDisplayFormat('yyyy-MM-dd'); self.payment_date.setDate(QDate.currentDate()); self.payment_date.setMinimumWidth(120)
        self.payment_amount = QDoubleSpinBox(); self.payment_amount.setFont(_best_currency_font()); self.payment_amount.setDecimals(2); self.payment_amount.setMaximum(1_000_000_000); self.payment_amount.setMinimum(0); self.payment_amount.setButtonSymbols(QDoubleSpinBox.NoButtons); self.payment_amount.setMinimumWidth(130); self.payment_amount.setPrefix('₴ '); self.payment_amount.setStyleSheet(CURRENCY_FONT_CSS); self.payment_amount.lineEdit().setFont(_best_currency_font()); self.payment_amount.lineEdit().setStyleSheet(CURRENCY_FONT_CSS)
        from PySide6.QtWidgets import QComboBox
        self.payment_currency = QComboBox(); self.payment_currency.setMinimumWidth(90); self.payment_currency.setStyleSheet(CURRENCY_FONT_CSS); self.payment_currency.setFont(_best_currency_font()); self.payment_currency.view().setFont(_best_currency_font()); self.payment_currency.view().setStyleSheet(CURRENCY_FONT_CSS); self.payment_currency.currentIndexChanged.connect(self._sync_payment_currency_prefix)
        self.payment_comment = QLineEdit(); self.payment_comment.setPlaceholderText('Коментар до оплати (необов’язково)')
        self.btn_add_payment = QPushButton('Внести оплату'); self.btn_add_payment.setObjectName('Primary'); self.btn_add_payment.setMinimumHeight(22); self.btn_add_payment.setMinimumWidth(150); self.btn_add_payment.setStyleSheet("QPushButton{background:#4FAF7A; color:#FFFFFF; border:1px solid #449C6E; border-radius:6px; padding:2px 12px; font-weight:600;} QPushButton:hover{background:#449C6E;} QPushButton:disabled{background:#A7D6BA; color:white; border:1px solid #8EC6A5;}"); self.btn_add_payment.clicked.connect(self._add_payment)
        lbl_pay_date = QLabel('Дата:'); lbl_pay_date.setStyleSheet("font-weight:700; color:#111827; background:transparent; border:none;")
        lbl_pay_sum = QLabel('Сума:'); lbl_pay_sum.setStyleSheet("font-weight:700; color:#111827; background:transparent; border:none;")
        add_pay.addWidget(lbl_pay_date)
        add_pay.addWidget(self.payment_date)
        add_pay.addWidget(lbl_pay_sum)
        add_pay.addWidget(self.payment_amount)
        add_pay.addWidget(self.payment_currency)
        add_pay.addWidget(self.payment_comment, 1)
        add_pay.addWidget(self.btn_add_payment)
        pay_l.addLayout(add_pay)

        self.payments_table = QTableWidget(0, 5)
        self.payments_table.setHorizontalHeaderLabels(['Дата', 'Сума', 'Валюта', 'Коментар', 'Видалити']); self.payments_table.setStyleSheet(CURRENCY_FONT_CSS); self.payments_table.setFont(_best_currency_font())
        self.payments_table.verticalHeader().setVisible(False)
        self.payments_table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.payments_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.payments_table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.payments_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.payments_table.setWordWrap(True)
        self.payments_table.setMouseTracking(True)
        self.payments_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.payments_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.payments_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
        self.payments_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
        self.payments_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch)
        self.payments_table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeToContents)
        self._payments_delegate = RowTintDelegate(self.payments_table)
        self.payments_table.setItemDelegate(self._payments_delegate)
        self.payments_table.cellEntered.connect(lambda row, _col: (self._payments_delegate.set_hover_row(row), self.payments_table.viewport().update()))
        pay_l.addWidget(self.payments_table)

        self.tabs.addTab(self.projects_tab, "Проєкти")
        self.tabs.addTab(self.payments_tab, "Оплати")
        self.documents_tab = ClientDocumentsTab(self.repo, lambda: self.client_id, self)
        self.tabs.addTab(self.documents_tab, "Документи")
        self.reports_tab = ReportHistoryTab(self.repo, 'client', lambda: self.client_id, self, generate_callback=self._open_report_dialog, settings_callback=self._open_settings_for_templates, show_templates_button=False)
        self.tabs.addTab(self.reports_tab, "Звіти")
        self.email_history_tab = EmailHistoryTab(self.repo, lambda: self.client_id, self)
        self.tabs.addTab(self.email_history_tab, "Email-історія")
        self.tabs.addTab(info, "Дані замовника")
        self.tabs.setCurrentIndex(0)

        self._wire_autosave()

    def _info_label(self, text: str) -> QLabel:
        lbl = QLabel(f"{text}:")
        lbl.setMinimumWidth(170)
        lbl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        lbl.setStyleSheet("font-weight:700; color:#1F2937; background:transparent; border:none; padding:0; margin:0;")
        return lbl

    def _info_label_with_note(self, text: str, note: str) -> QWidget:
        wrap = QWidget(); wrap.setStyleSheet("background:transparent; border:none;")
        lay = QVBoxLayout(wrap); lay.setContentsMargins(0, 0, 0, 0); lay.setSpacing(2)
        title = QLabel(f"{text}:"); title.setMinimumWidth(170); title.setAlignment(Qt.AlignRight | Qt.AlignTop); title.setStyleSheet("font-weight:700; color:#1F2937; background:transparent; border:none; padding:0; margin:0;")
        extra = QLabel(note); extra.setAlignment(Qt.AlignRight | Qt.AlignTop); extra.setStyleSheet("font-size:11px; color:#6B7280; background:transparent; border:none; padding:0; margin:0;")
        lay.addWidget(title); lay.addWidget(extra)
        return wrap

    def _make_table_button_holder(self, button: QPushButton) -> QWidget:
        holder = QWidget()
        holder.setAttribute(Qt.WA_StyledBackground, True)
        holder.setAutoFillBackground(False)
        holder.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        hlay = QHBoxLayout(holder)
        hlay.setContentsMargins(0, 0, 0, 0)
        hlay.setSpacing(0)
        hlay.setAlignment(Qt.AlignCenter)
        hlay.addWidget(button, 0, Qt.AlignCenter)
        return holder

    def _currency_lines(self, values: dict[str, float], label: str) -> str:
        body = self.repo.format_currency_map(values) if self.client_id else '₴ 0.00'
        return f"{label}:\n{body}"

    def _currency_symbol(self, code: str) -> str:
        code = str(code or 'UAH').upper()
        return {'UAH': '₴', 'USD': '$', 'EUR': '€'}.get(code, code)

    def _sync_payment_currency_prefix(self) -> None:
        cur = self.payment_currency.currentData() or self.payment_currency.currentText() or 'UAH'
        self.payment_amount.setPrefix(f"{self._currency_symbol(cur)} ")

    def _update_balance_labels(self) -> None:
        balance_map = self.repo.get_client_balance_map(int(self.client_id)) if self.client_id else {'UAH': 0.0}
        debt_map = self.repo.get_client_debt_map(int(self.client_id)) if self.client_id else {'UAH': 0.0}
        self.projects_balance_label.setText(self._currency_lines(balance_map, 'Баланс'))
        self.payments_balance_label.setText(self._currency_lines(balance_map, 'Баланс'))
        self.projects_debt_label.setText(self._currency_lines(debt_map, 'Борг'))

    def _toggle_project_folder(self, project_row: dict) -> None:
        try:
            project_id = int(project_row.get('id') or 0)
        except Exception:
            return
        existing_path = str(project_row.get('project_folder_path') or '')
        if existing_path and Path(existing_path).exists():
            open_folder(existing_path)
            return
        folder_path = ensure_project_folder(
            project_title=str(project_row.get('title') or '').strip(),
            company_name=(self.company_name.text() or self.title.text() or '').strip(),
            created_date=str(project_row.get('created_date') or ''),
            existing_path=existing_path,
        )
        self.repo.set_project_folder_path(project_id, folder_path)
        self._after_financial_change()
        open_folder(folder_path)

    def _refresh_projects(self) -> None:
        rows = self.repo.list_client_projects(self.client_id) if self.client_id and hasattr(self.repo, 'list_client_projects') else []
        self.projects_table.setRowCount(0)
        unpaid_exists = any(int(r.get('client_paid') or 0) == 0 for r in rows)
        any_payable = any(int(r.get('client_paid') or 0) == 0 and int(r.get('payment_possible') or 0) == 1 for r in rows)
        self._update_balance_labels()

        client_name = (self.company_name.text() or self.title.text() or '').strip()
        for row in rows:
            r = self.projects_table.rowCount(); self.projects_table.insertRow(r)
            work = str(row.get('work_status') or '').upper()
            paid = int(row.get('client_paid') or 0)
            amount = float(row.get('total_amount') or 0.0)
            can_pay = int(row.get('payment_possible') or 0) == 1
            actor_names = str(row.get('actor_names') or '').strip() or '-'
            vals = [str(row.get('project_number') or ''), str(row.get('created_date') or ''), client_name, str(row.get('title') or ''), actor_names, self.repo.format_money(amount, row.get('currency') or 'UAH'), '']
            key = self._project_color_key(row)
            for c, v in enumerate(vals):
                it = QTableWidgetItem(v)
                if c == 5:
                    it.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
                it.setData(RowTintDelegate.ROLE_BG_KEY, key)
                self.projects_table.setItem(r, c, it)

            btn_status = QPushButton('Повернути в роботу' if work == 'COMPLETED' else 'Завершити проєкт')
            btn_status.setFixedHeight(22)
            btn_status.setMinimumWidth(140)
            if work == 'COMPLETED':
                btn_status.setStyleSheet("QPushButton{background:#FFD8A8; border:1px solid #E5BF90; border-radius:6px; padding:2px 8px; color:#8E6129; font-weight:600;} QPushButton:hover{background:#E5BF90;}")
            else:
                btn_status.setStyleSheet("QPushButton{background:#4FAF7A; border:1px solid #459E6E; border-radius:6px; padding:2px 8px; color:#FFFFFF; font-weight:600;} QPushButton:hover{background:#459E6E;}")
            btn_status.clicked.connect(lambda _=False, pid=int(row['id']), cur_work=str(row.get('work_status') or 'IN_PROGRESS'): self._toggle_project_work_status(pid, cur_work))
            self.projects_table.setCellWidget(r, 6, self._make_table_button_holder(btn_status))

            btn = QPushButton('Скасувати оплату' if paid == 1 else 'Оплатити')
            btn.setFixedHeight(22)
            btn.setMinimumWidth(126)
            if paid == 1:
                btn.setStyleSheet("QPushButton{background:#4FAF7A; border:1px solid #459E6E; border-radius:6px; padding:2px 8px; color:#FFFFFF; font-weight:600;} QPushButton:hover{background:#459E6E;}")
            elif can_pay:
                btn.setStyleSheet("QPushButton{background:#FFD8A8; border:1px solid #E5BF90; border-radius:6px; padding:2px 8px; color:#8E6129; font-weight:600;} QPushButton:hover{background:#E5BF90;}")
            else:
                btn.setStyleSheet("QPushButton{background:#EBEBEB; border:1px solid #D7D7D7; border-radius:6px; padding:2px 8px; color:#6B7280; font-weight:600;}")
            btn.clicked.connect(lambda _=False, pid=int(row['id']), cur_paid=bool(paid): self._toggle_project_paid(pid, cur_paid))
            self.projects_table.setCellWidget(r, 7, self._make_table_button_holder(btn))

            folder_exists = bool(str(row.get('project_folder_path') or '').strip() and Path(str(row.get('project_folder_path'))).exists())
            btn_folder = QPushButton('Відкрити папку проєкту' if folder_exists else 'Створити папку для проєкту')
            btn_folder.setFixedHeight(22)
            btn_folder.setMinimumWidth(196)
            if folder_exists:
                btn_folder.setStyleSheet("QPushButton{background:#FFD8A8; border:1px solid #E5BF90; border-radius:6px; padding:2px 8px; color:#8E6129; font-weight:600;} QPushButton:hover{background:#E5BF90;}")
            else:
                btn_folder.setStyleSheet("QPushButton{background:#4FAF7A; border:1px solid #459E6E; border-radius:6px; padding:2px 8px; color:#FFFFFF; font-weight:600;} QPushButton:hover{background:#459E6E;}")
            btn_folder.clicked.connect(lambda _=False, project_row=dict(row): self._toggle_project_folder(project_row))
            self.projects_table.setCellWidget(r, 8, self._make_table_button_holder(btn_folder))

            btn_del = QPushButton('Видалити')
            btn_del.setFixedHeight(22)
            btn_del.setMinimumWidth(96)
            btn_del.setStyleSheet("QPushButton{color:#7C2D12; background:#FFA8A8; border:1px solid #E59494; border-radius:6px; padding:2px 8px; font-weight:600;} QPushButton:hover{background:#E59494;}")
            btn_del.clicked.connect(lambda _=False, pid=int(row['id']): self._delete_project_from_client_tab(pid))
            self.projects_table.setCellWidget(r, 9, self._make_table_button_holder(btn_del))

        self.projects_table.resizeColumnsToContents()
        self.projects_table.setColumnWidth(3, max(220, self.projects_table.columnWidth(3)))
        self.projects_table.setColumnWidth(4, max(180, self.projects_table.columnWidth(4)))
        self.projects_table.resizeRowsToContents()
        for row_idx in range(self.projects_table.rowCount()):
            self.projects_table.setRowHeight(row_idx, max(18, min(22, self.projects_table.rowHeight(row_idx))))
        self.btn_pay_all.setEnabled(unpaid_exists)
        if any_payable:
            self.btn_pay_all.setStyleSheet("QPushButton{background:#FFD8A8;color:#8E6129;border:1px solid #E5BF90;border-radius:6px;padding:2px 8px;font-weight:600;} QPushButton:hover{background:#E5BF90;}")
        else:
            self.btn_pay_all.setStyleSheet("QPushButton{background:#EBEBEB;color:#6B7280;border:1px solid #D7D7D7;border-radius:6px;padding:2px 8px;font-weight:600;}")

    def _refresh_payments(self) -> None:
        rows = self.repo.list_client_payments(self.client_id) if self.client_id else []
        self.payments_table.setRowCount(0)
        self._update_balance_labels()
        for row in rows:
            r = self.payments_table.rowCount(); self.payments_table.insertRow(r)
            cur = str(row.get('currency') or 'UAH')
            vals = [str(row.get('payment_date') or ''), self.repo.format_money(float(row.get('amount') or 0.0), cur), self.repo.get_currency_symbol(cur), str(row.get('comment') or '')]
            for c, v in enumerate(vals):
                it = QTableWidgetItem(v)
                if c == 1:
                    it.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
                elif c == 2:
                    it.setTextAlignment(Qt.AlignCenter | Qt.AlignVCenter)
                it.setData(RowTintDelegate.ROLE_BG_KEY, 'blue')
                self.payments_table.setItem(r, c, it)
            btn_del = QPushButton('Видалити')
            btn_del.setFixedHeight(22)
            btn_del.setMinimumWidth(96)
            btn_del.setStyleSheet("QPushButton{color:#7C2D12; background:#FFA8A8; border:1px solid #E59494; border-radius:6px; padding:2px 8px; font-weight:600;} QPushButton:hover{background:#E59494;}")
            btn_del.clicked.connect(lambda _=False, pay_id=int(row['id']): self._delete_payment(pay_id))
            self.payments_table.setCellWidget(r, 4, self._make_table_button_holder(btn_del))
        self.payments_table.resizeColumnsToContents()
        self.payments_table.setColumnWidth(2, max(80, self.payments_table.columnWidth(2)))
        self.payments_table.setColumnWidth(3, max(220, self.payments_table.columnWidth(3)))
        self.payments_table.resizeRowsToContents()
        for row_idx in range(self.payments_table.rowCount()):
            self.payments_table.setRowHeight(row_idx, max(18, min(22, self.payments_table.rowHeight(row_idx))))

    def _project_color_key(self, row: dict) -> str:
        work = str(row.get('work_status') or '').upper(); paid = int(row.get('client_paid') or 0)
        if work == 'IN_PROGRESS' and paid == 0: return 'green'
        if work == 'IN_PROGRESS' and paid == 1: return 'yellow'
        if work == 'COMPLETED' and paid == 0: return 'red'
        return 'gray'

    def _on_projects_hover(self, row: int, _col: int) -> None:
        self._projects_delegate.set_hover_row(row)
        self.projects_table.viewport().update()

    def _open_settings_for_templates(self, tab_key: str = "email_templates") -> None:
        try:
            from mi100.ui.main_window import MainWindow
            win = self.window()
            if hasattr(win, '_on_settings_clicked'):
                win._on_settings_clicked(tab_key)
                return
        except Exception:
            pass

    def _open_report_dialog(self) -> None:
        if not self.client_id:
            return
        dlg = ReportDialog(self.repo, 'client', int(self.client_id), self)
        if dlg.exec():
            self.reports_tab.refresh()


    def _toggle_project_work_status(self, project_id: int, current_status: str) -> None:
        new_status = 'IN_PROGRESS' if str(current_status or '').upper() == 'COMPLETED' else 'COMPLETED'
        self.repo.set_project_work_status(int(project_id), new_status)
        self._after_financial_change()

    def _toggle_project_paid(self, project_id: int, paid: bool) -> None:
        try:
            self.repo.set_project_client_paid(int(project_id), 0 if paid else 1)
        except ValueError:
            QMessageBox.warning(self, 'Недостатній баланс', 'Поточний баланс клієнта не дозволяє провести оплату цього проєкту')
            return
        self._after_financial_change()

    def _pay_all_possible(self) -> int:
        rows = self.repo.list_client_projects(self.client_id) if self.client_id else []
        changed = 0
        for row in rows:
            if int(row.get('client_paid') or 0) != 0:
                continue
            try:
                self.repo.set_project_client_paid(int(row['id']), 1)
                changed += 1
            except ValueError:
                continue
        return changed

    def _pay_all_projects(self) -> None:
        rows = self.repo.list_client_projects(self.client_id) if self.client_id else []
        unpaid = [r for r in rows if int(r.get('client_paid') or 0) == 0]
        if not unpaid:
            return
        insufficient = False
        for row in unpaid:
            try:
                self.repo.set_project_client_paid(int(row['id']), 1)
            except ValueError:
                insufficient = True
                break
        if insufficient:
            box = QMessageBox(self)
            box.setIcon(QMessageBox.Warning)
            box.setWindowTitle('Недостатній баланс')
            box.setText('Поточний баланс клієнта не дозволяє провести оплату всіх проєктів')
            pay_possible_btn = box.addButton('Оплатити всі можливі', QMessageBox.AcceptRole)
            box.addButton('Закрити', QMessageBox.RejectRole)
            box.exec()
            if box.clickedButton() is pay_possible_btn:
                self._pay_all_possible()
        self._after_financial_change()

    def _add_payment(self) -> None:
        if not self.client_id:
            QMessageBox.warning(self, 'Увага', 'Спочатку збережіть замовника.')
            return
        amount = float(self.payment_amount.value())
        if amount <= 0:
            QMessageBox.warning(self, 'Увага', 'Вкажіть суму оплати більше нуля.')
            return
        self.repo.add_client_payment(int(self.client_id), self.payment_date.date().toString('yyyy-MM-dd'), amount, self.payment_currency.currentData() or self.payment_currency.currentText() or 'UAH', self.payment_comment.text())
        self.payment_amount.setValue(0.0)
        self.payment_comment.clear()
        self.payment_currency.setCurrentIndex(0)
        self._after_financial_change()

    def _delete_payment(self, payment_id: int) -> None:
        box = QMessageBox.question(self, 'Видалення', 'Видалити цю оплату?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if box != QMessageBox.Yes:
            return
        self.repo.delete_client_payment(int(payment_id))
        self._after_financial_change()

    def _after_financial_change(self) -> None:
        self._refresh_projects()
        self._refresh_payments()
        self.documents_tab.refresh()
        self.reports_tab.refresh()
        self.email_history_tab.refresh()
        if self.on_updated:
            try:
                self.on_updated(self.client_id)
            except Exception:
                pass

    def _contract_prefix(self) -> str:
        client_name = sanitize_filename_part((self.company_name.text() or self.title.text() or f"client_{self.client_id or 'new'}").strip())
        return f"client_{int(self.client_id) if self.client_id else 0}_{client_name}"

    def _allowed_contract_suffixes(self) -> tuple[str, ...]:
        return ('.pdf', '.doc', '.docx')

    def _contract_files(self) -> list[Path]:
        if not self.client_id:
            return []
        prefix = self._contract_prefix()
        files = []
        for path in self.contracts_dir.iterdir():
            if not path.is_file():
                continue
            if path.suffix.lower() not in self._allowed_contract_suffixes():
                continue
            if path.stem.startswith(prefix):
                files.append(path)
        return sorted(files, key=lambda p: p.stat().st_mtime, reverse=True)

    def _active_contract_file(self) -> Optional[Path]:
        for path in self._contract_files():
            if 'Не актуальний' not in path.stem:
                return path
        return None

    def _update_contract_buttons(self) -> None:
        active = self._active_contract_file()
        has_active = bool(active and active.exists())
        self.btn_add_contract.setText('Видалити договір' if has_active else 'Додати договір')
        self.btn_add_contract.setObjectName('Danger' if has_active else 'Primary')
        self.btn_add_contract.style().unpolish(self.btn_add_contract)
        self.btn_add_contract.style().polish(self.btn_add_contract)
        self.btn_open_contract.setEnabled(has_active)
        self.btn_delete_contract.setVisible(False)
        self.btn_delete_contract.setEnabled(False)

    def _pick_contract_file(self) -> str:
        path, _ = QFileDialog.getOpenFileName(
            self,
            'Оберіть договір',
            '',
            'Текстові документи (*.pdf *.doc *.docx)'
        )
        return path or ''

    def _add_contract(self) -> None:
        active = self._active_contract_file()
        if active and active.exists():
            self._delete_contract()
            return
        if not self.client_id:
            QMessageBox.warning(self, 'Увага', 'Спочатку збережіть замовника.')
            return
        src = self._pick_contract_file()
        if not src:
            return
        src_path = Path(src)
        if src_path.suffix.lower() not in self._allowed_contract_suffixes():
            QMessageBox.warning(self, 'Неприпустимий формат', 'Дозволені лише файли PDF, DOC та DOCX.')
            return
        target = self.contracts_dir / f"{self._contract_prefix()}{src_path.suffix.lower()}"
        for old in self._contract_files():
            try:
                old.unlink()
            except Exception:
                pass
        shutil.copy2(src_path, target)
        self._update_contract_buttons()
        QMessageBox.information(self, 'Готово', 'Договір збережено.')

    def _open_contract(self) -> None:
        active = self._active_contract_file()
        if not active or not active.exists():
            self._update_contract_buttons()
            return
        QDesktopServices.openUrl(QUrl.fromLocalFile(str(active)))

    def _delete_contract(self) -> None:
        active = self._active_contract_file()
        if not active or not active.exists():
            self._update_contract_buttons()
            return
        answer = QMessageBox.question(self, 'Видалення', 'Видалити договір цього замовника?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if answer != QMessageBox.Yes:
            return
        try:
            active.unlink()
        except Exception:
            QMessageBox.warning(self, 'Помилка', 'Не вдалося видалити договір.')
            return
        self._update_contract_buttons()

    def _delete_client(self) -> None:
        if not self.client_id:
            return
        box = QMessageBox(self)
        box.setIcon(QMessageBox.Question)
        box.setWindowTitle('Підтвердження видалення')
        box.setText('Видалити замовника?\n\nУсі пов’язані проєкти, оплати та документи також будуть видалені.')
        yes_btn = box.addButton('Так', QMessageBox.YesRole)
        box.addButton('Ні', QMessageBox.NoRole)
        box.exec()
        if box.clickedButton() is not yes_btn:
            return
        self.repo.delete_client(self.client_id)
        if self.on_deleted:
            self.on_deleted(self.client_id)

    def _wire_autosave(self) -> None:
        def on_change(field: str, getter):
            self._pending[field] = getter()
            self._save_timer.start()
        self.company_name.textChanged.connect(lambda: (on_change('company_name', self.company_name.text), on_change('display_name', self.company_name.text)))
        self.responsible.textChanged.connect(lambda: on_change('responsible_person_name', self.responsible.text))
        self.phone.textChanged.connect(lambda: on_change('phone', self.phone.text))
        self.email.textChanged.connect(lambda: on_change('email', self.email.text))
        self.requisites_text.textChanged.connect(lambda: on_change('requisites_text', self.requisites_text.toPlainText))
        self.notes.textChanged.connect(lambda: on_change('notes', self.notes.toPlainText))

    def _flush_pending(self) -> None:
        if not self._pending:
            return
        company_name = str(self._pending.get('company_name', self.company_name.text()) or '').strip()
        display_name = str(self._pending.get('display_name', self.display_name.text()) or company_name).strip()
        if not self.client_id:
            if not company_name:
                self._has_user_input = self._snapshot_has_user_input(); return
            self.client_id = self.repo.create_client(
                display_name=display_name or company_name,
                company_name=company_name,
                responsible_person_name=str(self._pending.get('responsible_person_name', self.responsible.text()) or '').strip(),
                phone=str(self._pending.get('phone', self.phone.text()) or '').strip(),
                email=str(self._pending.get('email', self.email.text()) or '').strip(),
                requisites_text=str(self._pending.get('requisites_text', self.requisites_text.toPlainText()) or '').strip(),
                legal_block_text='',
                authorized_signer_name='',
                authorized_signer_position='',
                contract_number='',
                contract_date_text='',
                notes=str(self._pending.get('notes', self.notes.toPlainText()) or '').strip(),
            )
            self.delete_btn.setEnabled(True)
            self.title.setText(company_name)
        else:
            if 'display_name' in self._pending and not display_name:
                self._pending.pop('display_name', None)
            if self._pending:
                self.repo.update_client(self.client_id, **self._pending)
                self.title.setText(company_name or self.title.text())
        if self.on_updated and self.client_id:
            try:
                self.on_updated(self.client_id)
                self._refresh_projects(); self._refresh_payments(); self.documents_tab.refresh(); self._update_contract_buttons()
            except Exception:
                pass
        self._has_user_input = self._snapshot_has_user_input(); self._pending = {}

    def _load(self) -> None:
        if not self.client_id:
            self.title.setText(''); self.delete_btn.setEnabled(False); self.documents_tab.refresh(); self.reports_tab.refresh(); self.tabs.setCurrentIndex(5); self._refresh_projects(); self._refresh_payments(); self._update_contract_buttons(); return
        row = self.repo.conn.execute('SELECT * FROM clients WHERE id=?;', (self.client_id,)).fetchone()
        if not row:
            self.title.setText('Замовник (не знайдено)'); return
        self.title.setText(row['company_name'] or row['display_name'] or '')
        self.company_name.setText(row['company_name'] or '')
        self.display_name.setText(row['display_name'] or '')
        self.responsible.setText(row['responsible_person_name'] or '')
        self.phone.setText(row['phone'] or '')
        self.email.setText(row['email'] or '')
        self.requisites_text.setPlainText(row['requisites_text'] or '')
        self.notes.setPlainText(row['notes'] or '')
        self.payment_currency.clear()
        for cur in self.repo.get_active_currencies():
            label = f"{cur.get('symbol')} {cur.get('code')}"
            self.payment_currency.addItem(label, cur.get('code'))
        self._refresh_projects(); self._refresh_payments(); self.documents_tab.refresh(); self.reports_tab.refresh(); self.email_history_tab.refresh(); self._update_contract_buttons()

    def _snapshot_has_user_input(self) -> bool:
        return any([
            (self.company_name.text() or '').strip(), (self.responsible.text() or '').strip(), (self.phone.text() or '').strip(), (self.email.text() or '').strip(),
            (self.requisites_text.toPlainText() or '').strip(), (self.notes.toPlainText() or '').strip(),
        ])

    def can_close_panel(self) -> bool:
        if self.client_id:
            return True
        if self._snapshot_has_user_input() and not (self.company_name.text() or '').strip():
            QMessageBox.warning(self, 'Помилка', 'Назва компанії є обов’язковою для заповнення')
            self.tabs.setCurrentIndex(5)
            self.company_name.setFocus()
            return False
        return True


    def _confirm_project_delete_options(self) -> tuple[bool, bool]:
        dlg = QDialog(self)
        dlg.setWindowTitle('Видалення проєкту')
        lay = QVBoxLayout(dlg)
        lay.addWidget(QLabel('Оберіть варіант видалення:'))
        chk_with = QCheckBox('Видалити проєкт і папку з файлами проєкту')
        chk_keep = QCheckBox('Видалити проєкт зі списку, але залишити всі файли')
        chk_with.setChecked(True)
        def sync_from_with(state):
            if state:
                chk_keep.setChecked(False)
            elif not chk_keep.isChecked():
                chk_keep.setChecked(True)
        def sync_from_keep(state):
            if state:
                chk_with.setChecked(False)
            elif not chk_with.isChecked():
                chk_with.setChecked(True)
        chk_with.stateChanged.connect(sync_from_with)
        chk_keep.stateChanged.connect(sync_from_keep)
        lay.addWidget(chk_with)
        lay.addWidget(chk_keep)
        btns = QHBoxLayout()
        btns.addStretch(1)
        btn_yes = QPushButton('Видалити')
        btn_no = QPushButton('Скасувати')
        btn_yes.clicked.connect(dlg.accept)
        btn_no.clicked.connect(dlg.reject)
        btns.addWidget(btn_yes)
        btns.addWidget(btn_no)
        lay.addLayout(btns)
        if dlg.exec() != QDialog.Accepted:
            return False, False
        return True, bool(chk_with.isChecked())

    def _delete_project_from_client_tab(self, project_id: int) -> None:
        ok, delete_folder = self._confirm_project_delete_options()
        if not ok:
            return
        folder_path = self.repo.get_project_folder_path(int(project_id))
        self.repo.delete_project(int(project_id))
        if delete_folder and folder_path:
            try:
                p = Path(folder_path)
                if p.exists():
                    shutil.rmtree(p, ignore_errors=True)
            except Exception:
                pass
        self._after_financial_change()
