import uuid
import logging
import json
from datetime import datetime, date
from typing import Dict, Any, List, Optional
from PyQt6.QtWidgets import (
    QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QGroupBox,
    QLabel, QComboBox, QLineEdit, QPushButton, QTableWidget,
    QTableWidgetItem, QHeaderView, QCheckBox, QDialogButtonBox,
    QWidget, QMessageBox
)
from PyQt6.QtGui import QIcon, QRegularExpressionValidator
from PyQt6.QtCore import Qt, QTimer, QRegularExpression
from core.config import SUPPORTED_CURRENCIES, BASE_CURRENCY, get_default_depot_name
from core.db import BaseManager, CustomerHistoryEvent, DataManager, FinancialTransaction, Sale, CashAccount, CardAccount, StockItem, TaxApplication
from modules.stock import DepotManager
from modules.stock import StockManager, ProductDefinitionManager, AdvancedGoldEntryDialog
from modules.cash import CurrencyManager
from modules.calculations import log_islem
class SaleManager(BaseManager):
    def __init__(self, data_manager: DataManager):
        super().__init__(data_manager, "sales", Sale)
    def _custom_map_to_sql(self, item: Any, sql_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        if "products_sold" in sql_data:
            sql_data["products_json"] = sql_data.pop("products_sold") or []
        if "payment_method" in sql_data:
            sql_data["payment_method_summary"] = sql_data.pop("payment_method") or ""
        sql_data.pop("person_id", None)
        return sql_data
    def _custom_map_from_sql(self, row: Dict[str, Any]) -> Optional[Any]:
        def fmt_date(val):
            if not val:
                return ""
            if isinstance(val, (datetime, date)):
                return val.strftime("%d.%m.%Y")
            return str(val)
        products = []
        raw = row.get("products_json")
        if raw:
            try:
                products = json.loads(raw) if isinstance(raw, str) else raw
            except Exception:
                products = []
        try:
            total_amount = float(row.get("total_amount") or 0)
        except Exception:
            total_amount = 0.0
        return self.data_class(
            sale_date=fmt_date(row.get("sale_date")),
            customer_full_name=row.get("customer_full_name", "") or "",
            products_sold=products or [],
            total_amount=total_amount,
            currency=row.get("currency", "") or "",
            payment_method=row.get("payment_method_summary", "") or "",
            id=row.get("id") or str(uuid.uuid4()),
            created_at=str(row.get("created_at") or ""),
            updated_at=str(row.get("updated_at") or ""),
            profile_id=row.get("profile_id") or "",
            person_id="",
        )
class TransactionService:
    """
    Tüm POS (Satış/Alış) ve karmaşık işlem mantığını yöneten servis sınıfı.
    UI'dan tamamen izole edilmiştir.
    """
    def __init__(self, data_manager, managers: Dict[str, Any], currency_manager, current_user: Dict):
        self.db = data_manager.db
        self.managers = managers
        self.currency_manager = currency_manager
        self.current_user = current_user
    def process_pos_transaction(self, context: Dict[str, Any]) -> Dict[str, Any]:
        """
        Run POS sale/purchase transaction in a single DB transaction.
        """
        cart = context['cart']
        person_name = context['person_name']
        stock_type = context['stock_type']
        is_sale = context['is_sale']
        payment_breakdown = context['payment_breakdown']
        products_sold_data = context['products_sold_data']
        display_currency = context['display_currency']
        tax_snapshot = context.get('tax_snapshot') or {}
        details_json_raw = context.get('details_json') or []
        details_json = details_json_raw if isinstance(details_json_raw, list) else []

        def _to_float(value: Any, default: float = 0.0) -> float:
            if isinstance(value, (int, float)):
                return float(value)
            text = str(value or "").strip()
            if not text:
                return default
            if "," in text and "." in text:
                if text.rfind(",") > text.rfind("."):
                    text = text.replace(".", "").replace(",", ".")
                else:
                    text = text.replace(",", "")
            elif "," in text:
                text = text.replace(",", ".")
            try:
                return float(text)
            except Exception:
                return default

        def _to_barcode_list(raw: Any) -> List[str]:
            if isinstance(raw, list):
                return [str(x).strip() for x in raw if str(x).strip()]
            if isinstance(raw, str) and raw.strip():
                return [raw.strip()]
            return []

        detail_by_barcode: Dict[str, Dict[str, Any]] = {}
        for d in details_json:
            if not isinstance(d, dict):
                continue
            bc = str(d.get('barkod') or '').strip()
            if bc:
                detail_by_barcode[bc] = d

        if stock_type == "Product":
            current_stock_mgr = self.managers['stock_manager']
            current_def_mgr = self.managers['product_manager']
        elif stock_type == "Ziynet":
            current_stock_mgr = self.managers['ziynet_stock_manager']
            current_def_mgr = self.managers['ziynet_manager']
        else:
            current_stock_mgr = self.managers['scrap_stock_manager']
            current_def_mgr = self.managers['scrap_manager']

        transaction_date = datetime.now().strftime("%d.%m.%Y")
        event_date_iso = datetime.now().isoformat()

        product_names = [item.get('type', '') for item in cart]
        products_str = ", ".join(product_names)
        if len(products_str) > 50:
            products_str = products_str[:47] + "..."

        trans_label = "Satis" if is_sale else "Alis"
        desc = f"{products_str} ({trans_label}) - {person_name}"
        mode = f"{stock_type}{'Sale' if is_sale else 'Purchase'} (POS)"

        exchange_items: List[Dict[str, Any]] = []
        paid_total_display = 0.0
        on_credit_base_total = 0.0
        has_cash_or_card_payment = False
        for p in payment_breakdown:
            m_type = p.get('method_type')
            if m_type == "On Credit":
                on_credit_base_total += _to_float(p.get('amount_base', 0.0), 0.0)
                continue
            amt = _to_float(p.get('amount', p.get('total_val', 0)), 0.0)
            if amt <= 0:
                continue
            curr = p.get('currency', display_currency)
            if m_type == 'product':
                curr = p.get('account_id', 'Urun')
            if m_type in ("cash", "card"):
                has_cash_or_card_payment = True
            line_total = _to_float(p.get('amount_base', p.get('total_val', amt)), amt)
            paid_total_display += line_total
            trade_row = {
                'amount': amt,
                'type': curr,
                'method': m_type,
                'currency': curr,
                'total_val': p.get('total_val', amt),
                'amount_base': line_total,
            }
            gold_details = p.get('gold_details') or []
            if m_type == 'product' and gold_details:
                trade_row['gold_details'] = gold_details
                trade_row['gram'] = sum(_to_float(i.get('gram'), 0.0) for i in gold_details)
                trade_row['karats'] = sorted({
                    str(i.get('karat') or '').strip()
                    for i in gold_details
                    if str(i.get('karat') or '').strip()
                })
            exchange_items.append(trade_row)

        json_data: Dict[str, Any] = {
            "main_desc": desc,
            "mode": mode,
            "main_product": products_str,
            "main_qty": sum(_to_float(i.get('quantity', 0), 0.0) for i in cart),
            "main_depot": cart[0].get('depot_name', get_default_depot_name()) if cart else get_default_depot_name(),
            "details_json": details_json,
        }
        if exchange_items:
            json_data["exchange_items"] = exchange_items
        if isinstance(tax_snapshot, dict) and tax_snapshot:
            json_data["tax_snapshot"] = {
                "currency": tax_snapshot.get("currency"),
                "subtotal": _to_float(tax_snapshot.get("subtotal"), 0.0),
                "tax_total": _to_float(tax_snapshot.get("tax_total"), 0.0),
                "grand_total": _to_float(tax_snapshot.get("grand_total"), 0.0),
                "tax_profile_id": tax_snapshot.get("tax_profile_id", ""),
                "apply_taxes": bool(tax_snapshot.get("apply_taxes")),
            }
        json_desc = json.dumps(json_data, ensure_ascii=False)

        generated_barcodes: List[str] = []
        total_amount_base = sum(
            self.currency_manager._convert_to_base_currency(_to_float(i.get('subtotal'), 0.0), i.get('currency', display_currency))
            for i in cart
        )
        subtotal_display = _to_float(self.currency_manager._convert_to_display_currency(total_amount_base, display_currency), 0.0)
        grand_total_display = _to_float(tax_snapshot.get("grand_total"), subtotal_display) if isinstance(tax_snapshot, dict) else subtotal_display
        on_credit_display = _to_float(
            self.currency_manager._convert_to_display_currency(on_credit_base_total, display_currency),
            0.0,
        ) if on_credit_base_total > 0 else 0.0
        remaining_display = max(grand_total_display - paid_total_display, 0.0)
        is_cash_card_closed = has_cash_or_card_payment and on_credit_display <= 0.0001 and remaining_display <= 0.01
        if is_cash_card_closed:
            outstanding_display = 0.0
        else:
            outstanding_display = max(remaining_display, on_credit_display)
        if outstanding_display < 0.01:
            outstanding_display = 0.0

        sale_id: Optional[str] = str(uuid.uuid4()) if is_sale else None
        tax_application_id: Optional[str] = str(uuid.uuid4()) if is_sale else None

        with self.db.transaction_scope() as cursor:
            debt_val = outstanding_display if is_sale else 0.0
            credit_val = 0.0 if is_sale else outstanding_display
            new_event = CustomerHistoryEvent(
                person_full_name=person_name,
                event_date=event_date_iso,
                event_type=f"{stock_type} {trans_label} (POS)",
                debt_original=debt_val,
                credit_original=credit_val,
                currency=display_currency,
                description=json_desc,
                transaction_amount=outstanding_display,
                related_id=sale_id if is_sale else None,
            )
            self.managers['customer_history_manager'].add_item(new_event, cursor=cursor)

            for payment in payment_breakdown:
                method_type = payment.get('method_type')
                if method_type in ['cash', 'card']:
                    fin_trans_type = "Income" if is_sale else "Expense"
                    mgr = None
                    if method_type == 'cash':
                        mgr = self.managers['cash_managers'].get(payment.get('account_id'))
                    else:
                        mgr = self.managers['card_managers'].get(payment.get('account_id'))
                    if mgr:
                        total_val = _to_float(payment.get('total_val', payment.get('amount')), 0.0)
                        pay_curr = payment.get('currency', display_currency)
                        p_base = _to_float(
                            payment.get('base_amount', payment.get('amount_base', self.currency_manager._convert_to_base_currency(total_val, pay_curr))),
                            self.currency_manager._convert_to_base_currency(total_val, pay_curr),
                        )
                        ft = FinancialTransaction(
                            transaction_date=transaction_date,
                            transaction_type=fin_trans_type,
                            original_amount=total_val,
                            currency=pay_curr,
                            base_amount=p_base,
                            description=desc,
                            person_full_name=person_name,
                            related_id=new_event.id,
                        )
                        mgr.add_item(ft, cursor=cursor)
                elif method_type == 'product':
                    prod_name = payment.get('account_id')
                    qty_pay = _to_float(payment.get('amount'), 0.0)
                    depot = payment.get('depot_name') or get_default_depot_name()
                    gold_details = payment.get('gold_details') or []
                    if not prod_name or qty_pay <= 0:
                        continue
                    is_incoming_trade = is_sale
                    if is_incoming_trade:
                        p_def = self.managers['product_manager'].find_by_attribute('product_name', prod_name)
                        p_base = 0.0
                        if p_def:
                            p_base = self.currency_manager._convert_to_base_currency(p_def.default_purchase_price, p_def.purchase_currency)
                        if gold_details:
                            for detail in gold_details:
                                barcode = str(detail.get('barcode') or '').strip()
                                gram_val = _to_float(detail.get('gram'), 0.0)
                                karat_val = str(detail.get('karat') or '').strip()
                                if not barcode or gram_val <= 0:
                                    continue
                                self.managers['stock_manager'].add_item(StockItem(
                                    item_type=prod_name,
                                    quantity=1.0,
                                    quantity_gr=gram_val,
                                    unique_barcode=barcode,
                                    depot_name=depot,
                                    purchase_price_base=p_base,
                                ), cursor=cursor)
                                barcode_mgr = self.managers.get('barcode_history_manager')
                                if barcode_mgr:
                                    barcode_mgr.register_buy(
                                        barcode=barcode,
                                        p_type=prod_name,
                                        gram=gram_val,
                                        karat=karat_val,
                                        source=person_name,
                                        price=p_base,
                                        method="POS Takas Tahsilat",
                                        cursor=cursor,
                                    )
                        else:
                            self.managers['stock_manager'].add_or_update_stock(prod_name, qty_pay, p_base, depot, cursor=cursor)
                    else:
                        if gold_details:
                            barcodes = [
                                str(detail.get('barcode') or '').strip()
                                for detail in gold_details
                                if str(detail.get('barcode') or '').strip()
                            ]
                            if barcodes:
                                self.managers['stock_manager'].remove_stock_by_barcodes(barcodes, depot, cursor=cursor)
                                barcode_mgr = self.managers.get('barcode_history_manager')
                                if barcode_mgr:
                                    sell_price = _to_float(payment.get('unit_price'), 0.0)
                                    for detail in gold_details:
                                        barcode = str(detail.get('barcode') or '').strip()
                                        if not barcode:
                                            continue
                                        barcode_mgr.register_sell(
                                            barcode=barcode,
                                            target=person_name,
                                            price=sell_price,
                                            method="POS Takas Ödeme",
                                            cursor=cursor,
                                            depot=depot,
                                            sold_at=event_date_iso,
                                        )
                            else:
                                self.managers['stock_manager'].remove_stock(prod_name, qty_pay, depot, cursor=cursor)
                        else:
                            self.managers['stock_manager'].remove_stock(prod_name, qty_pay, depot, cursor=cursor)

            for item in products_sold_data:
                depot_name = item.get('depot_name', get_default_depot_name())
                product_name = item.get('type', '')
                item_qty = _to_float(item.get('quantity'), 0.0)
                item_curr = item.get('currency', display_currency)
                item_price = _to_float(item.get('unit_price'), 0.0)

                if is_sale:
                    if stock_type == "Product":
                        barcodes = _to_barcode_list(item.get('unique_barcode'))
                        if barcodes:
                            current_stock_mgr.remove_stock_by_barcodes(barcodes, depot_name, cursor=cursor)
                            for code in barcodes:
                                detail_row = detail_by_barcode.get(code, {})
                                sell_price = _to_float(detail_row.get('birim_fiyat'), item_price)
                                self.managers['barcode_history_manager'].register_sell(
                                    barcode=code,
                                    target=person_name,
                                    price=sell_price,
                                    method="POS Satis",
                                    cursor=cursor,
                                    depot=depot_name,
                                    sold_at=event_date_iso,
                                )
                        else:
                            current_stock_mgr.remove_stock(product_name, item_qty, depot_name, cursor=cursor)
                    else:
                        current_stock_mgr.remove_stock(product_name, item_qty, depot_name, cursor=cursor)

                    self.managers['stock_movement_manager'].add_log(
                        item_type=stock_type,
                        product_name=product_name,
                        op_type="Satis",
                        qty=item_qty,
                        source=depot_name,
                        dest=person_name,
                        desc="Satis Fisi",
                        user=self.current_user['name'],
                        cursor=cursor,
                    )
                else:
                    if stock_type == "Product":
                        manual_barcodes = _to_barcode_list(item.get('unique_barcode'))
                        if manual_barcodes:
                            for barcode in manual_barcodes:
                                detail_row = detail_by_barcode.get(barcode, {})
                                gram_val = _to_float(detail_row.get('gram'), _to_float(item.get('quantity_gr'), 0.0))
                                karat_val = str(detail_row.get('saflik') or item.get('karat') or '').strip()
                                if not karat_val:
                                    p_def = self.managers['product_manager'].find_by_attribute('product_name', product_name)
                                    karat_val = p_def.karat_purity if p_def else ""
                                line_unit_price = _to_float(detail_row.get('birim_fiyat'), item_price)
                                line_currency = str(detail_row.get('para_birimi') or item_curr or display_currency)
                                purchase_price_base = self.currency_manager._convert_to_base_currency(line_unit_price, line_currency)
                                new_stock = StockItem(
                                    item_type=product_name,
                                    quantity=1.0,
                                    quantity_gr=gram_val,
                                    unique_barcode=barcode,
                                    depot_name=depot_name,
                                    purchase_price_base=purchase_price_base,
                                )
                                current_stock_mgr.add_item(new_stock, cursor=cursor)
                                generated_barcodes.append(barcode)
                                self.managers['barcode_history_manager'].register_buy(
                                    barcode=barcode,
                                    p_type=product_name,
                                    gram=gram_val,
                                    karat=karat_val,
                                    source=person_name,
                                    price=purchase_price_base,
                                    method="POS Alis",
                                    cursor=cursor,
                                )
                                log_islem(f"{stock_type}_Purchase", 1.0, line_unit_price, 0, line_unit_price)
                        else:
                            purchase_price_base = self.currency_manager._convert_to_base_currency(item_price, item_curr)
                            current_stock_mgr.add_or_update_stock(product_name, item_qty, purchase_price_base, depot_name, cursor=cursor)
                    else:
                        purchase_price_base = self.currency_manager._convert_to_base_currency(item_price, item_curr)
                        current_stock_mgr.add_or_update_stock(product_name, item_qty, purchase_price_base, depot_name, cursor=cursor)

                    self.managers['stock_movement_manager'].add_log(
                        item_type=stock_type,
                        product_name=product_name,
                        op_type="Alis",
                        qty=item_qty,
                        source=person_name,
                        dest=depot_name,
                        desc="Alis Fisi",
                        user=self.current_user['name'],
                        cursor=cursor,
                    )

            if is_sale:
                payment_summary = ", ".join(p.get('account_name', p.get('method_type', '')) for p in payment_breakdown)
                new_sale = Sale(
                    sale_date=transaction_date,
                    customer_full_name=person_name,
                    products_sold=products_sold_data,
                    total_amount=grand_total_display,
                    currency=display_currency,
                    payment_method=payment_summary,
                    id=sale_id or str(uuid.uuid4()),
                )
                self.managers['sale_manager'].add_item(new_sale, cursor=cursor)

                tax_app_mgr = self.managers.get("tax_application_manager")
                if not tax_app_mgr:
                    raise RuntimeError("tax_application_manager is required for POS sales.")

                snapshot_for_storage: Dict[str, Any] = {}
                if isinstance(tax_snapshot, dict):
                    snapshot_for_storage = dict(tax_snapshot)
                if not snapshot_for_storage:
                    snapshot_for_storage = {
                        "version": 1,
                        "apply_taxes": False,
                        "currency": display_currency,
                        "customer_name": person_name,
                        "note": "",
                        "subtotal": float(subtotal_display or 0.0),
                        "tax_total": 0.0,
                        "grand_total": float(grand_total_display or 0.0),
                        "tax_profile_id": "",
                        "taxes": [],
                        "fixed_items": [],
                        "lines": [],
                    }
                snapshot_for_storage["source_table"] = "sales"
                snapshot_for_storage["source_id"] = str(sale_id or "")
                snapshot_for_storage["tax_application_id"] = str(tax_application_id or "")

                tax_app = TaxApplication(
                    id=tax_application_id or str(uuid.uuid4()),
                    source_table="sales",
                    source_id=sale_id or "",
                    subtotal=float(snapshot_for_storage.get("subtotal") or subtotal_display or 0.0),
                    tax_total=float(snapshot_for_storage.get("tax_total") or 0.0),
                    grand_total=float(snapshot_for_storage.get("grand_total") or grand_total_display or 0.0),
                    currency=display_currency,
                    snapshot_json=snapshot_for_storage,
                )
                tax_app_mgr.add_item(tax_app, cursor=cursor)

        if is_sale:
            return {
                "generated_barcodes": generated_barcodes,
                "sale_id": sale_id,
                "tax_application_id": tax_application_id,
            }
        return {"generated_barcodes": generated_barcodes}
class PaymentDialog(QDialog):
    """
    Geliştirilmiş ödeme Diyalogu (GüVENLi SüRüM)
    - Yetki kontrolü yapılmış hesapları ve depoları gösterir.
    - Kalan tutarı veresiye (Cari Hesap) olarak ekleme seçeneği sunar.
    """
    def __init__(self,
                 total_amount: float,
                 display_currency: str,
                 person_name: str,
                 cash_accounts: List[CashAccount],
                 card_accounts: List[CardAccount],
                 depot_manager: DepotManager,
                 product_manager: ProductDefinitionManager,
                 stock_manager: StockManager,
                 lang: dict,
                 currency_manager: CurrencyManager,
                 allowed_depots: List[str] = None,
                 product_trade_direction: str = "in",
                 allow_product_trade: bool = True,
                 allow_credit: bool = True,
                 allow_installments: bool = True,
                 parent=None):
        super().__init__(parent)
        self.total_amount = total_amount
        self.display_currency = display_currency
        self.person_name = person_name
        self.cash_accounts = cash_accounts
        self.card_accounts = card_accounts
        self.depot_manager = depot_manager
        self.product_manager = product_manager
        self.stock_manager = stock_manager
        self.allowed_depots = allowed_depots if allowed_depots is not None else []
        self.lang = lang
        self.currency_manager = currency_manager
        self.main_app = parent if hasattr(parent, "settings_manager") else getattr(parent, "main_app", None)
        self.settings_manager = getattr(self.main_app, "settings_manager", None)
        self.barcode_history_manager = getattr(self.main_app, "barcode_history_manager", None)
        self.product_trade_direction = "out" if str(product_trade_direction or "").strip().lower() == "out" else "in"
        self.allow_product_trade = bool(allow_product_trade)
        self.allow_credit = bool(allow_credit)
        self.allow_installments = bool(allow_installments)
        self.payments = []
        self.setWindowTitle(self.lang["payment_details"])
        self.setMinimumWidth(750)
        main_layout = QVBoxLayout(self)
        summary_group = QGroupBox("özet")
        summary_layout = QFormLayout(summary_group)
        self.total_amount_label = QLabel(f"<b>{self.currency_manager.format_currency(total_amount, display_currency)}</b>")
        self.paid_amount_label = QLabel(f"<b>{self.currency_manager.format_currency(0, display_currency)}</b>")
        self.remaining_amount_label = QLabel(f"<b>{self.currency_manager.format_currency(total_amount, display_currency)}</b>")
        summary_layout.addRow(f'{self.lang["total_amount"]}:', self.total_amount_label)
        summary_layout.addRow(f'{self.lang["total_payments"]}:', self.paid_amount_label)
        summary_layout.addRow(f'{self.lang["remaining_amount"]}:', self.remaining_amount_label)
        main_layout.addWidget(summary_group)
        entry_group = QGroupBox(self.lang.get("add_payment_part", "Add Payment Part"))
        entry_layout = QVBoxLayout(entry_group)
        type_layout = QHBoxLayout()
        type_layout.addWidget(QLabel(f"<b>{self.lang.get('payment_type', 'Payment Type')}:</b>"))
        self.payment_mode_combo = QComboBox()
        mode_items = [self.lang.get("cash_card", "Cash / Card")]
        if self.allow_product_trade:
            mode_items.append(self.lang.get("product_trade", "Product (Trade/Stock)"))
        self.payment_mode_combo.addItems(mode_items)
        self.payment_mode_combo.currentIndexChanged.connect(self._on_mode_changed)
        type_layout.addWidget(self.payment_mode_combo)
        type_layout.addStretch()
        entry_layout.addLayout(type_layout)
        self.cash_widget = QWidget()
        cash_layout = QHBoxLayout(self.cash_widget)
        cash_layout.setContentsMargins(0,0,0,0)
        self.account_combo = QComboBox()
        self.account_combo.setMinimumWidth(200)
        for acc in self.cash_accounts:
            self.account_combo.addItem(f"{self.lang['cash']} - {acc.name}", ("cash", acc.id))
        for acc in self.card_accounts:
            self.account_combo.addItem(f"{self.lang['card']} - {acc.name}", ("card", acc.id))
        self.cash_amount_entry = QLineEdit()
        self.cash_amount_entry.setPlaceholderText(self.lang.get("amount", "Amount"))
        self.cash_amount_entry.setValidator(self._create_float_validator())
        self.cash_amount_entry.returnPressed.connect(self._add_payment_part)
        self.cash_currency_combo = QComboBox()
        self.cash_currency_combo.addItems(SUPPORTED_CURRENCIES)
        self.cash_currency_combo.setCurrentText(self.display_currency)
        self.cash_currency_combo.currentTextChanged.connect(self._on_cash_currency_changed)
        cash_layout.addWidget(self.account_combo)
        cash_layout.addWidget(self.cash_amount_entry)
        cash_layout.addWidget(self.cash_currency_combo)
        entry_layout.addWidget(self.cash_widget)
        self.product_widget = QWidget()
        product_layout = QHBoxLayout(self.product_widget)
        product_layout.setContentsMargins(0,0,0,0)
        self.product_combo = QComboBox()
        self.product_combo.setEditable(True)
        self.product_combo.setMinimumWidth(150)
        self.product_combo.addItems(self.product_manager.get_product_names())
        self.product_combo.setPlaceholderText(self.lang.get("select_product", "Select Product"))
        self.product_combo.currentTextChanged.connect(self._on_product_selected)
        self.product_depot_combo = QComboBox()
        self.product_depot_combo.addItems(self.allowed_depots)
        self.product_depot_combo.setPlaceholderText(self.lang.get("depot", "Depot"))
        self.product_qty_entry = QLineEdit()
        self.product_qty_entry.setPlaceholderText(self.lang.get("quantity", "Quantity"))
        self.product_qty_entry.setFixedWidth(80)
        self.product_qty_entry.setValidator(self._create_float_validator(3))
        self.product_qty_entry.returnPressed.connect(self._add_payment_part)
        self.product_price_entry = QLineEdit()
        self.product_price_entry.setPlaceholderText(self.lang.get("unit_price_ph", "Unit Price"))
        self.product_price_entry.setFixedWidth(100)
        self.product_price_entry.setValidator(self._create_float_validator(2))
        self.product_price_entry.returnPressed.connect(self._add_payment_part)
        product_layout.addWidget(self.product_combo)
        product_layout.addWidget(self.product_depot_combo)
        product_layout.addWidget(self.product_qty_entry)
        product_layout.addWidget(self.product_price_entry)
        entry_layout.addWidget(self.product_widget)
        self.product_widget.setVisible(False)
        add_btn_layout = QHBoxLayout()
        add_btn_layout.addStretch()
        add_payment_btn = QPushButton(self.lang["add"])
        add_payment_btn.setIcon(QIcon.fromTheme("list-add"))
        add_payment_btn.clicked.connect(self._add_payment_part)
        add_btn_layout.addWidget(add_payment_btn)
        entry_layout.addLayout(add_btn_layout)
        main_layout.addWidget(entry_group)
        self.payments_table = QTableWidget(0, 5)
        headers = [self.lang.get("account_method", "Account / Method"), self.lang.get("detail_depot", "Detail (Depot)"), self.lang.get("quantity", "Quantity"), self.lang.get("amount_value", "Amount (Value)"), self.lang.get("delete", "Delete")]
        self.payments_table.setHorizontalHeaderLabels(headers)
        self.payments_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        main_layout.addWidget(self.payments_table)
        credit_group = QGroupBox()
        credit_layout = QHBoxLayout(credit_group)
        is_person_valid = (self.person_name and self.person_name != "N/A" and self.person_name.strip() != "")
        self.credit_checkbox = QCheckBox(self.lang.get("process_as_credit", "Process remaining to '{person_name}' as CREDIT").format(person_name=self.person_name))
        self.credit_checkbox.setStyleSheet("font-weight: bold; color: #D32F2F;")
        self.credit_checkbox.setEnabled(is_person_valid and self.allow_credit)
        self.credit_checkbox.setVisible(self.allow_credit)
        if not is_person_valid and self.allow_credit:
            self.credit_checkbox.setText(self.lang.get("select_person_for_credit", "Please select a Customer/Person for credit."))
        self.credit_checkbox.toggled.connect(self._update_totals)
        credit_layout.addWidget(self.credit_checkbox)
        main_layout.addWidget(credit_group)
        self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        self.buttons.accepted.connect(self.accept)
        self.buttons.rejected.connect(self.reject)
        ok_btn = self.buttons.button(QDialogButtonBox.StandardButton.Ok)
        if ok_btn:
            ok_btn.setDefault(True)
            ok_btn.setAutoDefault(True)
        main_layout.addWidget(self.buttons)
        QTimer.singleShot(0, self._auto_fill_remaining)
    def _create_float_validator(self, decimals=2) -> QRegularExpressionValidator:
        if decimals <= 0:
            pattern = r"^\d*$"
        else:
            pattern = rf"^$|^\d+([.,]\d{{0,{decimals}}})?$"
        return QRegularExpressionValidator(QRegularExpression(pattern))
    def _convert_between_currencies(self, amount: float, from_currency: str, to_currency: str) -> float:
        try:
            amount_f = float(amount or 0.0)
        except Exception:
            amount_f = 0.0
        from_curr = str(from_currency or "").strip() or self.display_currency
        to_curr = str(to_currency or "").strip() or self.display_currency
        if amount_f <= 0 or from_curr == to_curr:
            return amount_f
        try:
            rate = self.currency_manager.get_exchange_rate(from_curr, to_curr)
            if rate and rate > 0:
                return amount_f * float(rate)
        except Exception:
            pass
        return amount_f
    def _get_payment_applied_amount(self, payment: Dict[str, Any]) -> float:
        try:
            return float(payment.get('amount_base', payment.get('total_val', payment.get('amount', 0.0))) or 0.0)
        except Exception:
            return 0.0
    def _get_selected_cash_currency(self) -> str:
        return str(self.cash_currency_combo.currentText() or self.display_currency)
    def _on_cash_currency_changed(self, _currency: str) -> None:
        if self.payment_mode_combo.currentIndex() != 0:
            return
        self._auto_fill_remaining()
    def _on_mode_changed(self, index):
        is_product_mode = self.allow_product_trade and index == 1
        self.cash_widget.setVisible(not is_product_mode)
        self.product_widget.setVisible(is_product_mode)
        if not is_product_mode:
            self._auto_fill_remaining()
    def _on_product_selected(self, product_name):
        if not product_name: return
        product = self.product_manager.find_by_attribute("product_name", product_name)
        if product:
            self.product_price_entry.setText(f"{product.default_purchase_price:.2f}")
    def _is_gold_product(self, product_name: str) -> bool:
        return bool(product_name and self.product_manager.find_by_attribute("product_name", product_name))
    def _is_outgoing_trade(self) -> bool:
        return self.product_trade_direction == "out"
    def _get_available_trade_qty(self, product_name: str, depot_name: str) -> float:
        stock_item = self.stock_manager.get_stock_by_type_and_depot(product_name, depot_name) if product_name and depot_name else None
        return float(getattr(stock_item, "quantity", 0.0) or 0.0)
    def _collect_gold_trade_details(
        self,
        product_name: str,
        quantity: int,
        depot_name: str,
    ) -> Optional[List[Dict[str, Any]]]:
        if quantity <= 0:
            return []
        if self._is_outgoing_trade():
            available_qty = self._get_available_trade_qty(product_name, depot_name)
            if available_qty + 0.0001 < quantity:
                QMessageBox.warning(
                    self,
                    self.lang["warning"],
                    f"Yetersiz stok. '{depot_name}' deposunda {available_qty:.3f} adet var.",
                )
                return None
        dialog_mode = "Out" if self._is_outgoing_trade() else "In"
        dialog = AdvancedGoldEntryDialog(
            dialog_mode,
            product_name,
            quantity,
            self.stock_manager,
            self.product_manager,
            self,
        )
        if dialog.exec() != QDialog.DialogCode.Accepted:
            return None
        details = dialog.get_details() or []
        if len(details) != quantity:
            QMessageBox.warning(
                self,
                self.lang["warning"],
                "Altın takas detayları eksik veya adet ile eşleşmiyor.",
            )
            return None
        return details
    def _add_payment_part(self):
        """
        ödeme parçası ekler (Nakit veya ürün).
        FAZ 5: Girdi doğrulama (Input Validation) ve Sanity Check eklendi.
        """
        mode_index = self.payment_mode_combo.currentIndex()
        payment_part = None
        if mode_index == 0: # Nakit/Kart
            amount_str = self.cash_amount_entry.text().replace(',', '.')
            if not amount_str: return
            try:
                amount = float(amount_str)
                if amount <= 0:
                    QMessageBox.warning(self, self.lang["warning"], "Tutar 0'dan büyük olmalıdır.")
                    return
                if amount > 1_000_000_000:
                    QMessageBox.warning(self, self.lang["warning"], "Girilen tutar çok yüksek (1 Milyar+). Lütfen kontrol edin.")
                    return
                acc_data = self.account_combo.currentData()
                if not acc_data:
                    QMessageBox.warning(self, self.lang["warning"], "Lütfen bir hesap seçin.")
                    return
                selected_currency = self.cash_currency_combo.currentText()
                applied_amount = self._convert_between_currencies(amount, selected_currency, self.display_currency)
                system_base_amount = self._convert_between_currencies(amount, selected_currency, BASE_CURRENCY)
                payment_part = {
                    'method_type': acc_data[0],
                    'account_id': acc_data[1],
                    'account_name': self.account_combo.currentText(),
                    'amount': amount,
                    'original_amount': amount,
                    'currency': selected_currency,
                    'total_val': amount,
                    'amount_base': applied_amount,
                    'base_amount': system_base_amount,
                    'display_detail': "-"
                }
            except ValueError:
                QMessageBox.warning(self, self.lang["error"], "Geçersiz sayı formatı.")
                return
        else: # ürün (Takas)
            prod_name = self.product_combo.currentText().strip()
            qty_str = self.product_qty_entry.text().replace(',', '.')
            price_str = self.product_price_entry.text().replace(',', '.')
            depot_name = self.product_depot_combo.currentText()
            if not all([prod_name, qty_str, price_str, depot_name]):
                QMessageBox.warning(self, self.lang["warning"], "Lütfen tüm alanları doldurun.")
                return
            try:
                qty = float(qty_str)
                price = float(price_str)
                if qty <= 0:
                    QMessageBox.warning(self, self.lang["warning"], "Miktar 0'dan büyük olmalıdır.")
                    return
                if price < 0:
                    QMessageBox.warning(self, self.lang["warning"], "Fiyat negatif olamaz.")
                    return
                gold_details: List[Dict[str, Any]] = []
                if self._is_outgoing_trade():
                    available_qty = self._get_available_trade_qty(prod_name, depot_name)
                    if available_qty + 0.0001 < qty:
                        QMessageBox.warning(
                            self,
                            self.lang["warning"],
                            f"Yetersiz stok. '{depot_name}' deposunda {available_qty:.3f} adet var.",
                        )
                        return
                if self._is_gold_product(prod_name):
                    qty_int = int(round(qty))
                    if abs(qty - qty_int) > 0.0001:
                        QMessageBox.warning(
                            self,
                            self.lang["warning"],
                            "Altın takasında adet tam sayı olmalıdır.",
                        )
                        return
                    gold_details = self._collect_gold_trade_details(prod_name, qty_int, depot_name)
                    if gold_details is None:
                        return
                    qty = float(qty_int)
                detail_suffix = f"{depot_name} ({price:.2f})"
                if gold_details:
                    total_gram = sum(float(item.get("gram") or 0.0) for item in gold_details)
                    detail_suffix = f"{detail_suffix} | {total_gram:.3f} gr"
                payment_part = {
                    'method_type': 'product',
                    'account_id': prod_name,
                    'account_name': f"ürün: {prod_name}",
                    'amount': qty,
                    'quantity': qty,
                    'product_name': prod_name,
                    'unit_price': price,
                    'currency': self.display_currency,
                    'total_val': qty * price,
                    'amount_base': qty * price,
                    'base_amount': qty * price,
                    'depot_name': depot_name,
                    'display_detail': detail_suffix,
                    'gold_details': gold_details,
                    'stock_direction': self.product_trade_direction,
                }
            except ValueError:
                QMessageBox.warning(self, self.lang["error"], "Geçersiz sayı formatı.")
                return
        if payment_part:
            self.payments.append(payment_part)
            self._update_payments_table()
            self._update_totals()
            self.cash_amount_entry.clear()
            self.product_qty_entry.clear()
            remaining = self._calculate_remaining()
            if remaining > 0:
                self._auto_fill_remaining()
            else:
                self.buttons.button(QDialogButtonBox.StandardButton.Ok).setFocus()
    def _calculate_remaining(self) -> float:
        """Kalan tutarı gösterim para birimi ile döndürür."""
        paid_amount_base = sum(self._get_payment_applied_amount(p) for p in self.payments)
        total_amount_base = float(self.total_amount or 0.0)
        remaining_base = total_amount_base - paid_amount_base
        if remaining_base < 0:
            remaining_base = 0.0
        if self.credit_checkbox.isChecked():
            return 0.0
        try:
            return max(0.0, float(remaining_base))
        except Exception:
            return 0.0
    def _auto_fill_remaining(self):
        """Kalan tutarı seçili ödeme para birimine çevirip giriş kutusuna yazar."""
        remaining_disp = self._calculate_remaining()
        target_currency = self._get_selected_cash_currency()
        remaining_in_selected = self._convert_between_currencies(remaining_disp, self.display_currency, target_currency)
        if remaining_in_selected > 0:
            self.cash_amount_entry.setText(f"{remaining_in_selected:.2f}")
            self.cash_amount_entry.setFocus()
            self.cash_amount_entry.selectAll()
    def _update_payments_table(self):
        self.payments_table.setRowCount(len(self.payments))
        for i, payment in enumerate(self.payments):
            self.payments_table.setItem(i, 0, QTableWidgetItem(payment['account_name']))
            self.payments_table.setItem(i, 1, QTableWidgetItem(payment.get('display_detail', '-')))
            amount_str = f"{payment['amount']:,.3f}" if payment['method_type'] == 'product' else f"{payment['amount']:,.2f}"
            self.payments_table.setItem(i, 2, QTableWidgetItem(f"{amount_str} {payment.get('currency', '') if payment['method_type'] != 'product' else ''}"))
            applied_amount = self._get_payment_applied_amount(payment)
            self.payments_table.setItem(i, 3, QTableWidgetItem(f"{applied_amount:,.2f} {self.display_currency}"))
            remove_btn = QPushButton(self.lang["delete"])
            remove_btn.setObjectName("dangerButton")
            remove_btn.clicked.connect(lambda _, r=i: self._remove_payment_part(r))
            self.payments_table.setCellWidget(i, 4, remove_btn)
    def _remove_payment_part(self, row):
        if 0 <= row < len(self.payments):
            del self.payments[row]
            self._update_payments_table()
            self._update_totals()
            self._auto_fill_remaining()
    def _update_totals(self):
        paid_amount_base = sum(self._get_payment_applied_amount(p) for p in self.payments)
        total_amount_base = float(self.total_amount or 0.0)
        remaining_base = total_amount_base - paid_amount_base
        if self.credit_checkbox.isChecked() and remaining_base > 0:
            remaining_base = 0.0
        paid_amount_display = paid_amount_base
        remaining_display = remaining_base
        self.paid_amount_label.setText(f"<b>{self.currency_manager.format_currency(paid_amount_display, self.display_currency)}</b>")
        self.remaining_amount_label.setText(f"<b>{self.currency_manager.format_currency(remaining_display, self.display_currency)}</b>")
    def get_payment_breakdown(self) -> List[Dict[str, Any]]:
        breakdown = self.payments.copy()
        if self.credit_checkbox.isChecked():
            paid_amount_base = sum(self._get_payment_applied_amount(p) for p in self.payments)
            total_amount_base = float(self.total_amount or 0.0)
            remaining_base = total_amount_base - paid_amount_base
            if remaining_base > 0.001:
                breakdown.append({'method_type': 'On Credit', 'amount_base': remaining_base})
        return breakdown
    def accept(self):
        paid_amount_base = sum(self._get_payment_applied_amount(p) for p in self.payments)
        total_amount_base = float(self.total_amount or 0.0)
        if paid_amount_base > total_amount_base + 0.01:
            QMessageBox.warning(self, self.lang["warning"], self.lang["payment_exceeds_total"])
            return
        if not self.credit_checkbox.isChecked() and abs(total_amount_base - paid_amount_base) > 0.01:
            QMessageBox.warning(self, self.lang["warning"], "ödenen tutar toplamla eşleşmiyor. Lütfen 'Kalanı Veresiye Ekle' kutusunu işaretleyin veya ödemeyi tamamlayın.")
            return
        super().accept()





