from __future__ import annotations
import os
import json
import subprocess
import sys
import uuid
from datetime import datetime
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
from pathlib import Path
from typing import Any, Dict, List, Optional, Literal, Tuple
from fpdf import FPDF
from PyQt6.QtWidgets import QFileDialog, QMessageBox
from core.config import FONT_PATH, get_app_data_directory, logger, load_language, get_active_language_code # FONT_PATH import edildi
from core.db import DataManager, SettingsManager
DocType = Literal["invoice", "receipt"]
def _to_decimal(value: Any, default: Decimal = Decimal("0")) -> Decimal:
    if value is None:
        return default
    if isinstance(value, Decimal):
        return value
    if isinstance(value, (int, float)):
        return Decimal(str(value))
    if isinstance(value, str):
        s = value.strip()
        if not s:
            return default
        s = s.replace(",", ".")
        try:
            return Decimal(s)
        except InvalidOperation:
            return default
    try:
        return Decimal(str(value))
    except Exception:
        return default
def _money(value: Decimal) -> Decimal:
    return value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
def _fmt_money(value: Decimal) -> str:
    v = _money(value)
    return f"{v:.2f}".replace(".", ",")
def _fmt_qty(value: Decimal) -> str:
    v = value.quantize(Decimal("0.001"), rounding=ROUND_HALF_UP)
    s = f"{v:f}"
    if "." in s:
        s = s.rstrip("0").rstrip(".")
    return s.replace(".", ",")
def _safe_open_file(path: str) -> None:
    try:
        if os.name == "nt":
            os.startfile(path)  # type: ignore[attr-defined]
        else:
            opener = "open" if sys.platform == "darwin" else "xdg-open"
            subprocess.call([opener, path])
    except Exception:
        return
def get_next_document_no(db, profile_id: str, doc_type: DocType, prefix: str) -> str:
    from core.db import queue_row_sync_event, fetch_row_snapshot_by_id, SYNC_ACTION_ADD, SYNC_ACTION_UPDATE
    if not profile_id:
        raise ValueError("profile_id is required")
    if doc_type not in ("invoice", "receipt"):
        raise ValueError(f"Unsupported doc_type: {doc_type}")
    clean_prefix = (prefix or "DOC").strip() or "DOC"
    year = datetime.now().year
    with db.transaction_scope() as cur:
        cur.execute(
            "SELECT id, last_no FROM document_counters WHERE profile_id=%s AND doc_type=%s FOR UPDATE",
            (profile_id, doc_type),
        )
        row = cur.fetchone()
        if row:
            doc_id = row.get("id")
            last_no = int(row.get("last_no") or 0)
            next_no = last_no + 1
            before_row = fetch_row_snapshot_by_id(db, "document_counters", doc_id, profile_id, cur)
            cur.execute("UPDATE document_counters SET last_no=%s WHERE id=%s", (next_no, doc_id))
            after_row = fetch_row_snapshot_by_id(db, "document_counters", doc_id, profile_id, cur)
            queue_row_sync_event(db, "document_counters", profile_id, SYNC_ACTION_UPDATE, doc_id, before_row, after_row, cur)
        else:
            next_no = 1
            doc_id = str(uuid.uuid4())
            cur.execute(
                "INSERT INTO document_counters (id, profile_id, doc_type, last_no) VALUES (%s, %s, %s, %s)",
                (doc_id, profile_id, doc_type, next_no),
            )
            after_row = fetch_row_snapshot_by_id(db, "document_counters", doc_id, profile_id, cur)
            queue_row_sync_event(db, "document_counters", profile_id, SYNC_ACTION_ADD, doc_id, None, after_row, cur)
    return f"{clean_prefix}-{year}-{next_no}"
class DocumentPDF(FPDF):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._font_added = False

    def add_font_if_needed(self) -> None:
        if self._font_added:
            return
        
        # DEĞİŞİKLİK: Doğrudan config'deki yolu kullan
        font_to_use = FONT_PATH 

        if not Path(font_to_use).exists():
            logger.warning(f"Font file '{font_to_use}' not found. Falling back to Arial.")
            return
        
        try:
            self.add_font("DejaVu", "", font_to_use, uni=True)
            self.add_font("DejaVu", "B", font_to_use, uni=True)
            self._font_added = True
        except Exception as e:
            logger.error(f"Font could not be loaded ({font_to_use}): {e}")
    # HATA ÇÖZÜMÜ: Property ismini değiştirdik (font_family -> effective_font_family)
    @property
    def effective_font_family(self) -> str:
        return "DejaVu" if self._font_added else "Arial"
class DocumentService:
    def __init__(self, data_manager: DataManager, settings_manager: SettingsManager, parent=None):
        self.data_manager = data_manager
        self.settings_manager = settings_manager
        self.db = data_manager.db
        self.profile_id = data_manager.profile_id
        self.parent = parent
        self.lang = getattr(parent, "lang", None) or load_language(get_active_language_code())

    def _show_message(self, icon: QMessageBox.Icon, title: str, text: str) -> None:
        box = QMessageBox(self.parent)
        box.setIcon(icon)
        box.setWindowTitle(title)
        box.setText(text)
        ok_btn = box.addButton(self.lang.get("ok", "OK"), QMessageBox.ButtonRole.AcceptRole)
        ok_btn.setDefault(True)
        box.exec()
    def _refresh_company_info(self) -> Dict[str, str]:
        try:
            if hasattr(self.settings_manager, "_load_all_settings"):
                self.settings_manager._load_all_settings()
            elif hasattr(self.settings_manager, "_load_settings"):
                fresh = self.settings_manager._load_settings()
                if isinstance(fresh, dict):
                    if hasattr(self.settings_manager, "settings"):
                        self.settings_manager.settings = fresh
                    elif hasattr(self.settings_manager, "_cache"):
                        self.settings_manager._cache = fresh
        except Exception as e:
            logger.warning(f"Company info refresh failed, using cached settings: {e}")
        return self.settings_manager.get_company_info()
    def _validate_company_info(self, company_info: Dict[str, str]) -> bool:
        company_info = self._refresh_company_info() or company_info or {}
        required_fields = [
            ("company_name", self.lang.get("company_name", "Company Name")),
            ("tax_office", self.lang.get("tax_office", "Tax Office")),
            ("tax_number", self.lang.get("tax_number", "Tax Number")),
        ]
        missing = []
        for key, label in required_fields:
            if not str((company_info or {}).get(key, "") or "").strip():
                missing.append(label)
        if missing:
            self._show_message(
                QMessageBox.Icon.Warning,
                self.lang.get("missing_info", "Missing Info"),
                self.lang.get(
                    "missing_company_fields_before_print",
                    "Before printing, fill these fields in Settings > Shop Information:\n{fields}",
                ).format(fields="\n".join(f"- {k}" for k in missing)),
            )
            return False
        return True
    def _get_tax_profile_name(self, tax_profile_id: str) -> str:
        tax_profile_id = (tax_profile_id or "").strip()
        if not tax_profile_id:
            return ""
        try:
            row = self.db.fetch_one(
                "SELECT name FROM tax_profiles WHERE profile_id=%s AND id=%s LIMIT 1",
                (self.profile_id, tax_profile_id),
            )
            return str((row or {}).get("name") or "").strip()
        except Exception:
            return ""

    def _load_tax_snapshot(
        self,
        *,
        tax_application_id: str = "",
        source_table: str = "",
        source_id: str = "",
    ) -> Optional[Dict[str, Any]]:
        tax_application_id = (tax_application_id or "").strip()
        source_table = (source_table or "").strip()
        source_id = (source_id or "").strip()

        if tax_application_id:
            row = self.db.fetch_one(
                "SELECT snapshot_json FROM tax_applications WHERE profile_id=%s AND id=%s LIMIT 1",
                (self.profile_id, tax_application_id),
            )
        elif source_table and source_id:
            row = self.db.fetch_one(
                """
                SELECT snapshot_json
                FROM tax_applications
                WHERE profile_id=%s AND source_table=%s AND source_id=%s
                ORDER BY created_at DESC
                LIMIT 1
                """,
                (self.profile_id, source_table, source_id),
            )
        else:
            return None

        if not row:
            return None

        snapshot = row.get("snapshot_json")
        if isinstance(snapshot, str):
            try:
                snapshot = json.loads(snapshot)
            except Exception:
                snapshot = None
        if not isinstance(snapshot, dict):
            return None
        return snapshot
    def _render_pdf(
        self,
        path: str,
        doc_type: DocType,
        doc_no: str,
        created_at: datetime,
        company_info: Dict[str, str],
        transaction_data: Dict[str, Any],
        snapshot: Dict[str, Any],
    ) -> None:
        pdf = DocumentPDF(orientation="P", unit="mm", format="A4")
        pdf.add_font_if_needed()
        pdf.set_auto_page_break(auto=True, margin=12)
        pdf.add_page()
        ff = pdf.effective_font_family
        currency = (snapshot.get("currency") or transaction_data.get("currency") or "TRY").strip() or "TRY"
        pdf.set_font(ff, "B", 14)
        pdf.cell(0, 8, company_info.get("company_name", ""), ln=1, align="C")
        pdf.set_font(ff, "", 9)
        addr = company_info.get("company_address", "")
        if addr:
            pdf.multi_cell(0, 5, addr, align="C")
        tax_line = self.lang.get(
            "tax_office_and_number",
            "Tax Office: {office} - Tax Number: {no}",
        ).format(
            office=company_info.get("tax_office", ""), no=company_info.get("tax_number", "")
        )
        pdf.cell(0, 5, tax_line, ln=1, align="C")
        contact_parts = []
        if company_info.get("company_phone"):
            contact_parts.append(f"Tel: {company_info['company_phone']}")
        if company_info.get("company_email"):
            contact_parts.append(company_info["company_email"])
        if contact_parts:
            pdf.cell(0, 5, " | ".join(contact_parts), ln=1, align="C")
        pdf.ln(2)
        pdf.set_draw_color(160, 160, 160)
        pdf.line(pdf.l_margin, pdf.get_y(), pdf.w - pdf.r_margin, pdf.get_y())
        pdf.ln(4)
        doc_label = (
            self.lang.get("document_invoice_label", "Invoice")
            if doc_type == "invoice"
            else self.lang.get("document_receipt_label", "Retail Sales Receipt")
        )
        pdf.set_font(ff, "B", 12)
        pdf.cell(0, 7, doc_label, ln=1)
        pdf.set_font(ff, "", 10)
        pdf.cell(0, 6, f"{self.lang.get('document_no', 'Document No')}: {doc_no}", ln=1)
        pdf.cell(0, 6, f"{self.lang.get('date_time', 'Date/Time')}: {created_at.strftime('%d.%m.%Y %H:%M')}", ln=1)
        customer = transaction_data.get("customer") or {}
        cust_name = (customer.get("name") or customer.get("full_name") or "").strip()
        if cust_name:
            pdf.cell(0, 6, f"{self.lang.get('customer', 'Customer')}: {cust_name}", ln=1)
        note = (transaction_data.get("note") or "").strip()
        if note:
            pdf.multi_cell(0, 6, f"{self.lang.get('note', 'Note')}: {note}")
        tax_profile_name = ""
        if snapshot.get("apply_taxes"):
            tax_profile_name = self._get_tax_profile_name(str(snapshot.get("tax_profile_id") or ""))
        if tax_profile_name:
            pdf.cell(0, 6, f"{self.lang.get('tax_profile', 'Tax Profile')}: {tax_profile_name}", ln=1)
        pdf.ln(2)
        avail = pdf.w - pdf.l_margin - pdf.r_margin
        lines = snapshot.get("lines") or []
        taxes = snapshot.get("taxes") or []
        try:
            taxes = sorted(
                taxes,
                key=lambda t: (int(t.get("priority") or 0), str(t.get("name") or "").lower()),
            )
        except Exception:
            pass
        show_line_tax = any(
            _to_decimal((l or {}).get("line_tax_total"), default=Decimal("0")) != 0 for l in (lines or [])
        )
        w_qty = 18
        w_unit = 24
        w_net = 26
        w_tax = 22
        w_total = 26
        if show_line_tax:
            w_name = max(40, avail - (w_qty + w_unit + w_net + w_tax + w_total))
        else:
            w_name = max(40, avail - (w_qty + w_unit + w_total))
        pdf.set_font(ff, "B", 9)
        if show_line_tax:
            headers = [
                self.lang.get("product", "Product"),
                self.lang.get("quantity", "Quantity"),
                self.lang.get("unit_col", "Unit"),
                self.lang.get("net_amount", "Net"),
                self.lang.get("tax", "Tax"),
                self.lang.get("total", "Total"),
            ]
            widths = [w_name, w_qty, w_unit, w_net, w_tax, w_total]
        else:
            headers = [
                self.lang.get("product", "Product"),
                self.lang.get("quantity", "Quantity"),
                self.lang.get("unit_col", "Unit"),
                self.lang.get("amount", "Amount"),
            ]
            widths = [w_name, w_qty, w_unit, w_total]
        for h, w in zip(headers, widths):
            pdf.cell(w, 7, h, border=1, align="C")
        pdf.ln()
        pdf.set_font(ff, "", 9)
        for line in lines:
            name = str((line or {}).get("name") or "")
            if len(name) > 42:
                name = name[:40] + "…"
            pdf.cell(w_name, 7, name, border=1)
            qty = _to_decimal((line or {}).get("qty"), default=Decimal("0"))
            unit_price = _to_decimal((line or {}).get("unit_price"), default=Decimal("0"))
            line_subtotal = _to_decimal((line or {}).get("line_subtotal"), default=Decimal("0"))
            line_tax_total = _to_decimal((line or {}).get("line_tax_total"), default=Decimal("0"))
            line_total = _to_decimal((line or {}).get("line_total"), default=Decimal("0"))
            pdf.cell(w_qty, 7, _fmt_qty(qty), border=1, align="R")
            pdf.cell(w_unit, 7, _fmt_money(unit_price), border=1, align="R")
            if show_line_tax:
                pdf.cell(w_net, 7, _fmt_money(line_subtotal), border=1, align="R")
                pdf.cell(w_tax, 7, _fmt_money(line_tax_total), border=1, align="R")
                pdf.cell(w_total, 7, _fmt_money(line_total), border=1, align="R")
            else:
                pdf.cell(w_total, 7, _fmt_money(line_total), border=1, align="R")
            pdf.ln()
        pdf.ln(2)
        pdf.set_font(ff, "B", 10)
        right_w = w_total
        label_w = avail - right_w
        subtotal = _to_decimal(snapshot.get("subtotal"), default=Decimal("0"))
        tax_total = _to_decimal(snapshot.get("tax_total"), default=Decimal("0"))
        grand_total = _to_decimal(snapshot.get("grand_total"), default=Decimal("0"))
        pdf.cell(label_w, 7, self.lang.get("subtotal", "Subtotal"), border=0, align="R")
        pdf.cell(right_w, 7, f"{_fmt_money(subtotal)} {currency}", border=0, align="R", ln=1)
        for t in taxes or []:
            amt = _to_decimal((t or {}).get("amount"), default=Decimal("0"))
            if amt == 0:
                continue
            t_name = str((t or {}).get("name") or "").strip() or self.lang.get("tax", "Tax")
            t_type = str((t or {}).get("type") or "").strip().lower()
            t_rate = _to_decimal((t or {}).get("rate"), default=Decimal("0"))
            if t_type == "percent":
                rate_str = _fmt_qty(t_rate.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
                label = f"{t_name} %{rate_str}"
            else:
                label = t_name
            pdf.cell(label_w, 7, label, border=0, align="R")
            pdf.cell(right_w, 7, f"{_fmt_money(amt)} {currency}", border=0, align="R", ln=1)
        if tax_total != 0:
            pdf.cell(label_w, 7, self.lang.get("total_tax", "Total Tax"), border=0, align="R")
            pdf.cell(right_w, 7, f"{_fmt_money(tax_total)} {currency}", border=0, align="R", ln=1)
        pdf.cell(label_w, 8, self.lang.get("grand_total", "Grand Total"), border=0, align="R")
        pdf.cell(right_w, 8, f"{_fmt_money(grand_total)} {currency}", border=0, align="R", ln=1)
        pdf.output(path)
    def create_document_from_transaction(self, transaction_data: Dict[str, Any], doc_type: DocType) -> Optional[str]:
        try:
            if doc_type not in ("invoice", "receipt"):
                self._show_message(
                    QMessageBox.Icon.Critical,
                    self.lang.get("error", "Error"),
                    f"{self.lang.get('invalid_document_type', 'Invalid document type')}: {doc_type}",
                )
                return None
            company_info = self._refresh_company_info()
            if not self._validate_company_info(company_info):
                return None
            snapshot = transaction_data.get("tax_snapshot")
            if not isinstance(snapshot, dict):
                tax_application_id = str(transaction_data.get("tax_application_id") or "").strip()
                source_table = str(transaction_data.get("source_table") or transaction_data.get("source") or "").strip()
                source_id = str(transaction_data.get("source_id") or "").strip()
                snapshot = self._load_tax_snapshot(
                    tax_application_id=tax_application_id, source_table=source_table, source_id=source_id
                )
            if not isinstance(snapshot, dict) or not snapshot:
                QMessageBox.warning(
                    self.parent,
                    "Uyarı",
                    "Belge oluşturmak için vergi özeti (snapshot) bulunamadı.\n"
                    "Lütfen işlemi kaydedip tekrar deneyin.",
                )
                return None
            doc_no = get_next_document_no(self.db, self.profile_id, doc_type, company_info.get("document_prefix", "DOC"))
            created_at = datetime.now()
            if not (snapshot.get("lines") or []):
                QMessageBox.warning(self.parent, "Uyarı", "Belge oluşturmak için en az 1 kalem gereklidir.")
                return None
            filename = f"{doc_type}_{doc_no}_{created_at.strftime('%Y%m%d_%H%M%S')}.pdf".replace(" ", "_")
            save_path, _ = QFileDialog.getSaveFileName(self.parent, "Belge Kaydet", filename, "PDF Files (*.pdf)")
            if not save_path:
                return None
            self._render_pdf(
                save_path,
                doc_type,
                doc_no,
                created_at,
                company_info,
                transaction_data,
                snapshot,
            )
            _safe_open_file(save_path)
            return save_path
        except Exception as e:
            logger.error(f"Document create failed: {e}", exc_info=True)
            QMessageBox.critical(self.parent, "Hata", f"Belge oluşturulamadı: {e}")
            return None




