
import uuid
import logging
import json
import hashlib
import threading
from contextlib import contextmanager
from datetime import datetime, date
from decimal import Decimal
from typing import List, Dict, Any, Optional, Union
from dataclasses import dataclass, field, asdict
from typing import get_args, get_origin
from typing import List, Dict, Any, Optional, Union, Tuple

from core.config import BASE_CURRENCY, logger
from core.db_factory import get_database_adapter
from core.tenant_manager import TenantManager

SYNC_ACTION_ADD = "ADD"
SYNC_ACTION_UPDATE = "UPDATE"
SYNC_ACTION_DELETE = "DELETE"
SYNC_ACTION_ROLLBACK = "ROLLBACK"

_SYNC_ACTIONS = {
    SYNC_ACTION_ADD,
    SYNC_ACTION_UPDATE,
    SYNC_ACTION_DELETE,
    SYNC_ACTION_ROLLBACK,
}

_SYNC_CONTEXT = threading.local()

class SyncJSONEncoder(json.JSONEncoder):
    """JSON encoder used by local sync queue payloads."""
    def default(self, obj: Any): 
        if isinstance(obj, (datetime, date)):
            return obj.isoformat()
        if isinstance(obj, Decimal):
            return str(obj)
        if isinstance(obj, uuid.UUID):
            return str(obj)
        return super().default(obj)

def _is_safe_identifier(name: str) -> bool:
    return bool(name) and name.replace("_", "").isalnum()

def _is_profile_sync_enabled() -> bool:
    try:
        return TenantManager().is_profile_sync_enabled()
    except Exception:
        return False

def is_sync_replay_mode() -> bool:
    return bool(getattr(_SYNC_CONTEXT, "replay_mode", False))

@contextmanager
def sync_replay_mode(enabled: bool = True):
    prev = bool(getattr(_SYNC_CONTEXT, "replay_mode", False))
    _SYNC_CONTEXT.replay_mode = bool(enabled)
    try:
        yield
    finally:
        _SYNC_CONTEXT.replay_mode = prev

def fetch_row_snapshot_by_id(
    db,
    table_name: str,
    record_id: Optional[str],
    profile_id: Optional[str] = None,
    cursor=None,
) -> Optional[Dict[str, Any]]:
    if not record_id or not _is_safe_identifier(table_name):
        return None
    if profile_id:
        sql = f"SELECT * FROM `{table_name}` WHERE id = %s AND profile_id = %s LIMIT 1"
        params: Tuple[Any, ...] = (record_id, profile_id)
    else:
        sql = f"SELECT * FROM `{table_name}` WHERE id = %s LIMIT 1"
        params = (record_id,)

    if cursor is not None:
        cursor.execute(sql, params)
        row = cursor.fetchone()
    else:
        row = db.fetch_one(sql, params)
    return dict(row) if row else None

def fetch_rows_by_ids(
    db,
    table_name: str,
    profile_id: str,
    record_ids: List[str],
    cursor=None,
) -> Dict[str, Dict[str, Any]]:
    if not record_ids or not _is_safe_identifier(table_name):
        return {}

    uniq_ids = [rid for rid in dict.fromkeys(record_ids) if rid]
    if not uniq_ids:
        return {}

    placeholders = ", ".join(["%s"] * len(uniq_ids))
    sql = (
        f"SELECT * FROM `{table_name}` WHERE profile_id = %s "
        f"AND id IN ({placeholders})"
    )
    params: Tuple[Any, ...] = tuple([profile_id] + uniq_ids)

    if cursor is not None:
        cursor.execute(sql, params)
        rows = cursor.fetchall() or []
    else:
        rows = db.fetch_all(sql, params) or []

    result: Dict[str, Dict[str, Any]] = {}
    for row in rows:
        rid = str(row.get("id") or "")
        if rid:
            result[rid] = dict(row)
    return result

def enqueue_sync_event(
    db,
    table_name: str,
    action_type: str,
    record_id: Optional[str],
    payload: Dict[str, Any],
    cursor=None,
    transaction_uuid: Optional[str] = None,
) -> Optional[str]:
    if is_sync_replay_mode():
        return None
    if not _is_profile_sync_enabled():
        return None
    if not _is_safe_identifier(table_name):
        return None

    act = str(action_type or "").upper().strip()
    if act not in _SYNC_ACTIONS:
        return None

    tx_uuid = str(transaction_uuid or uuid.uuid4())
    payload_json = json.dumps(payload or {}, cls=SyncJSONEncoder, ensure_ascii=False)

    sql = """
        INSERT INTO local_sync_queue
            (transaction_uuid, table_name, action_type, record_id, payload_json, created_at)
        VALUES (%s, %s, %s, %s, %s, %s)
    """
    params = (
        tx_uuid,
        table_name,
        act,
        str(record_id or ""),
        payload_json,
        datetime.now(),
    )
    if cursor is not None:
        cursor.execute(sql, params)
    else:
        db.execute_non_query(sql, params)
    return tx_uuid

def queue_row_sync_event(
    db,
    table_name: str,
    profile_id: str,
    action_type: str,
    record_id: Optional[str],
    before_row: Optional[Dict[str, Any]],
    after_row: Optional[Dict[str, Any]],
    cursor=None,
    transaction_uuid: Optional[str] = None,
    meta: Optional[Dict[str, Any]] = None,
) -> Optional[str]:
    payload = {
        "profile_id": profile_id,
        "before": before_row,
        "after": after_row,
        "meta": meta or {},
    }
    return enqueue_sync_event(
        db=db,
        table_name=table_name,
        action_type=action_type,
        record_id=record_id,
        payload=payload,
        cursor=cursor,
        transaction_uuid=transaction_uuid,
    )

def setting_record_id(profile_id: str, setting_key: str) -> str:
    seed = f"{str(profile_id or '').strip().lower()}:{str(setting_key or '').strip().lower()}"
    digest = hashlib.sha1(seed.encode("utf-8")).hexdigest()[:32]
    return f"{digest[0:8]}-{digest[8:12]}-{digest[12:16]}-{digest[16:20]}-{digest[20:32]}"

@dataclass
class Category:
    name: str
    profile_id: str = ""
    id: str = field(default_factory=lambda: str(uuid.uuid4()))

@dataclass
class ProductDefinition:
    product_name: str
    category: str = "Altin"
    barcode: str = ""
    karat_purity: str = ""
    workmanship_fee: float = 0.0
    workmanship_currency: str = "TRY"
    default_purchase_price: float = 0.0
    default_sale_price: float = 0.0
    purchase_currency: str = "TRY"
    sale_currency: str = "TRY"
    is_active: bool = True
    default_weight_gr: float = 0.0
    stock_type: str = "Product"
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""
    category_id: Optional[str] = None

@dataclass
class TaxDefinition:
    name: str
    rate: float
    type: str  
    country: Optional[str] = None
    active: bool = True
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class TaxProfile:
    name: str
    description: str = ""
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class TaxProfileItem:
    tax_profile_id: str
    tax_id: str
    priority: int = 0
    enabled_by_default: bool = True
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class TaxApplication:
    source_table: str
    source_id: str
    subtotal: float
    tax_total: float
    grand_total: float
    currency: str
    snapshot_json: Dict[str, Any] = field(default_factory=dict)
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class Person:
    full_name: str
    phone_number: str = ""
    address: str = ""
    notes: str = ""
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    profile_id: str = ""
    created_at: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    updated_at: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    def to_dict(self):
        return asdict(self)

@dataclass
class StockItem:
    item_type: str
    quantity: float
    depot_name: str
    stock_type: str = "Product"
    purchase_price_base: float = 0.0
    unique_barcode: str = ""
    quantity_gr: float = 0.0
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class StockMovement:
    date: str
    item_type: str
    product_name: str
    operation_type: str
    quantity: float
    source: str
    destination: str
    description: str
    user: str = "Admin"
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class BarcodeHistory:
    barcode: str
    product_type: str
    gram: float
    karat: str
    buy_date: str = ""
    buy_from: str = ""
    buy_price: float = 0.0
    buy_method: str = ""
    is_sold: bool = False
    sell_date: str = ""
    sell_to: str = ""
    sell_price: float = 0.0
    sell_method: str = ""
    movements: List[Dict[str, Any]] = field(default_factory=list)
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class FinancialTransaction:
    transaction_date: str
    transaction_type: str
    original_amount: float
    currency: str
    base_amount: float
    description: str = ""
    person_full_name: Optional[str] = None
    related_id: Optional[str] = None
    account_id: str = ""
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class DebtRecord:
    record_type: str
    person_full_name: str
    transaction_date: str
    debt_type: str
    original_amount: float = 0.0
    currency: str = BASE_CURRENCY
    total_amount_base: float = 0.0
    product_type: Optional[str] = None
    product_quantity: float = 0.0
    description: str = ""
    status: str = "Active"
    paid_amount_base: float = 0.0
    paid_quantity: float = 0.0
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    due_date: Optional[str] = None
    is_paid: bool = False
    payment_date: Optional[str] = None
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""
    person_id: str = ""

@dataclass
class RepairRecord:
    customer_full_name: str
    product_description: str
    complaint_request: str
    receipt_date: str
    estimated_fee: float = 0.0
    fee_currency: str = BASE_CURRENCY
    status: str = "Received"
    delivery_date: str = ""
    notes: str = ""
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""
    person_id: str = ""

@dataclass
class Sale:
    sale_date: str
    customer_full_name: str
    products_sold: List[Dict[str, Any]]
    total_amount: float
    currency: str
    payment_method: str
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""
    person_id: str = ""

@dataclass
class CardAccount:
    name: str
    type: str = "card"
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class CashAccount:
    name: str
    type: str = "cash"
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class Depot:
    name: str
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class ScheduledTransaction:
    due_date: str
    transaction_type: str
    amount: float
    currency: str
    description: str
    target: str
    person_full_name: Optional[str] = None
    status: str = 'Pending'
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""

@dataclass
class CustomerHistoryEvent:
    person_full_name: str
    event_date: str
    event_type: str
    description: str
    debt_original: float = 0.0
    credit_original: float = 0.0
    currency: Optional[str] = None
    related_id: Optional[str] = None
    transaction_amount: float = 0.0
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    profile_id: str = ""
    person_id: str = ""

@dataclass
class ServiceDefinition:
    """
    [AI_DOC]
    Hizmet Tanimlama Sinifi. FAZ 5 ile birlikte asamalar (stages) eklendi.
    [/AI_DOC]
    """
    name: str
    custom_columns: List[str] = field(default_factory=list)
    stages: List[str] = field(default_factory=lambda: ["Alindi", "Islemde", "Hazir", "Teslim Edildi"])
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    profile_id: str = ""

@dataclass
class ServiceJob:
    service_def_id: str
    customer_name: str
    description: str
    status: str
    fee: float
    currency: str
    dynamic_data: Dict[str, str] = field(default_factory=dict)
    start_date: str = field(default_factory=lambda: datetime.now().strftime("%d.%m.%Y"))
    delivery_date: str = ""
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    profile_id: str = ""
class DataManager:
    def __init__(self, profile_file_path: Union[str, Any] = None, base_dir: Union[str, Any] = None):
        self.profile_name: Optional[str] = None
        if profile_file_path:
            try:
                import os
                self.profile_name = os.path.splitext(os.path.basename(str(profile_file_path)))[0] or None
            except:
                self.profile_name = str(profile_file_path) or None

        self.db = get_database_adapter()
        self.profile_id: str = ""

        if self.profile_name:
            self.profile_id = self._get_profile_id(self.profile_name)
            if not self.profile_id:
                raise ValueError(f"Profile not found: {self.profile_name}")
            logger.info(f"DataManager baslatildi. Profil: {self.profile_name} (ID: {self.profile_id})")
        else:
            logger.info("DataManager baslatildi. Aktif profil secilmedi.")

    def _get_profile_id(self, name: str) -> str:
        sql = "SELECT id FROM profiles WHERE name = %s AND COALESCE(is_deleted, 0) = 0"
        result = self.db.fetch_one(sql, (name,))
        if result:
            return result['id']
        return ""

    def load_data(self, key: str, data_class: Optional[Any] = None, default_value: Any = None) -> Any:
        if key == "agenda_notes":
            query = "SELECT note_key, note_value FROM agenda_notes WHERE profile_id = %s"
            rows = self.db.fetch_all(query, (self.profile_id,))
            result = {}
            for r in rows:
                try: result[r['note_key']] = json.loads(r['note_value'])
                except: result[r['note_key']] = r['note_value']
            return result
        return default_value

    def save_data(self, key: str, data: Any) -> None:
        if key == "agenda_notes":
            with self.db.transaction_scope() as cursor:
                for date_key, note_content in data.items():
                    val_str = json.dumps(note_content, ensure_ascii=False)
                    
                    # 1. Eski veriyi (Before) al
                    cursor.execute("SELECT profile_id, note_key, note_value FROM agenda_notes WHERE profile_id = %s AND note_key = %s LIMIT 1", (self.profile_id, date_key))
                    before_row = cursor.fetchone()
                    
                    # 2. Veriyi kaydet
                    query = """
                        INSERT INTO agenda_notes (profile_id, note_key, note_value)
                        VALUES (%s, %s, %s)
                        ON DUPLICATE KEY UPDATE note_value = VALUES(note_value)
                    """
                    cursor.execute(query, (self.profile_id, date_key, val_str))
                    
                    # 3. Yeni veriyi (After) al
                    cursor.execute("SELECT profile_id, note_key, note_value FROM agenda_notes WHERE profile_id = %s AND note_key = %s LIMIT 1", (self.profile_id, date_key))
                    after_row = cursor.fetchone()
                    
                    # 4. SENKRONİZASYON KUYRUĞUNA BİLDİR (KAÇAK KAPATILDI)
                    rec_id = setting_record_id(self.profile_id, f"agenda:{date_key}")
                    queue_row_sync_event(
                        db=self.db,
                        table_name="agenda_notes",
                        profile_id=self.profile_id,
                        action_type=SYNC_ACTION_UPDATE if before_row else SYNC_ACTION_ADD,
                        record_id=rec_id,
                        before_row=dict(before_row) if before_row else None,
                        after_row=dict(after_row) if after_row else None,
                        cursor=cursor,
                        transaction_uuid=str(uuid.uuid4()),
                        meta={"source": "DataManager.save_data"}
                    )

class BaseManager:
    def __init__(self, data_manager: DataManager, data_key: str, data_class: Any):
        self.data_manager = data_manager
        self.db = data_manager.db
        self.profile_id = data_manager.profile_id
        self.data_class = data_class
        self.filter_account_id = None
        if "cash_transactions_" in data_key or "card_transactions_" in data_key:
            try:
                self.filter_account_id = data_key.split("_")[-1]
            except:
                self.filter_account_id = None
        self.table_name = self._map_key_to_table(data_key)
        self.data = [] 

    def get_page(self, page: int = 1, page_size: int = 50, filters: Dict[str, Any] = None) -> Tuple[List[Any], int]:
        if page is None: page = 1
        try: page = int(page)
        except (TypeError, ValueError): page = 1
        if page < 1: page = 1

        if page_size is None: page_size = 50
        try: page_size = int(page_size)
        except (TypeError, ValueError): page_size = 50
        if page_size < 1: page_size = 50

        offset = (page - 1) * page_size
        if offset < 0: offset = 0

        if getattr(self.data_manager, "is_aggregate", False):
            if not getattr(self.data_manager, "profile_ids", None):
                return [], 0
            placeholders = ", ".join(["%s"] * len(self.data_manager.profile_ids))
            sql = f"SELECT * FROM {self.table_name} WHERE profile_id IN ({placeholders})"
            params = list(self.data_manager.profile_ids)
        else:
            sql = f"SELECT * FROM {self.table_name} WHERE profile_id = %s"
            params = [self.profile_id]

        if (
            self.table_name in ["stock_items", "financial_transactions", "sales", "debt_records"]
            and self.db.has_column(self.table_name, "is_deleted")
        ):
            sql += " AND is_deleted = 0"

        if filters:
            for key, value in filters.items():
                if value is not None:
                    sql += f" AND {key} = %s"
                    params.append(value)

        count_sql = sql.replace("SELECT *", "SELECT COUNT(*)")
        total_count = self.db.fetch_one(count_sql, tuple(params)).get('COUNT(*)', 0)

        assert offset >= 0, f"OFFSET must be >= 0 (page={page}, page_size={page_size}, offset={offset})"

        sql += f" LIMIT {page_size} OFFSET {offset}"
        rows = self.db.fetch_all(sql, tuple(params))
        
        results = []
        for row in rows:
            obj = self._map_sql_to_dataclass(row)
            if obj:
                if getattr(self.data_manager, "is_aggregate", False) and hasattr(obj, 'profile_id'):
                    obj._branch_name = self.data_manager.profile_map.get(obj.profile_id, "Bilinmeyen Şube")
                results.append(obj)

        return results, total_count

    def _map_key_to_table(self, key: str) -> str:
        mapping = {
            "categories": "categories",
            "products": "product_definitions",
            "ziynet_products": "product_definitions",
            "scrap_products": "product_definitions",
            "persons": "persons",
            "stock": "stock_items",
            "ziynet_stock": "stock_items",
            "scrap_stock": "stock_items",
            "stock_movements": "stock_movements",
            "barcode_history": "barcode_history",
            "debt_records": "debt_records",
            "repairs": "repair_records",
            "sales": "sales",
            "cash_accounts": "financial_accounts",
            "card_accounts": "financial_accounts",
            "depots": "depots",
            "scheduled_transactions": "scheduled_transactions",
            "customer_history": "customer_history_events",
            "service_definitions": "service_definitions",
            "service_jobs": "service_jobs",
            "agenda_notes": "agenda_notes"
        }
        if "cash_transactions" in key or "card_transactions" in key:
            return "financial_transactions"
        return mapping.get(key, key)

    def _map_dataclass_to_sql_fields(self, item: Any) -> Dict[str, Any]:
        data = asdict(item)
        sql_data = data.copy()
        sql_data['profile_id'] = self.profile_id

        date_fields = [
            "transaction_date", "sale_date", "buy_date", "sell_date",
            "due_date", "payment_date", "receipt_date", "start_date",
            "delivery_date", "event_date", "date",
        ]
        datetime_fields = {"buy_date", "sell_date", "event_date", "date"}

        def parse_date_value(field_name: str, value: Any):
            if value is None: return None
            if isinstance(value, str):
                s = value.strip()
                if not s: return None
                for fmt in ("%d.%m.%Y %H:%M:%S", "%d.%m.%Y %H:%M", "%d.%m.%Y"):
                    try:
                        dt = datetime.strptime(s, fmt)
                        return dt if field_name in datetime_fields else dt.date()
                    except ValueError: pass
                try:
                    if "T" in s or ":" in s:
                        dt = datetime.fromisoformat(s.replace("Z", ""))
                        return dt if field_name in datetime_fields else dt.date()
                except ValueError: pass
                try:
                    d = date.fromisoformat(s[:10])
                    return datetime.combine(d, datetime.min.time()) if field_name in datetime_fields else d
                except ValueError:
                    return value
            if isinstance(value, datetime):
                return value if field_name in datetime_fields else value.date()
            if isinstance(value, date):
                return datetime.combine(value, datetime.min.time()) if field_name in datetime_fields else value
            return value

        for field_name in date_fields:
            if field_name in sql_data:
                sql_data[field_name] = parse_date_value(field_name, sql_data.get(field_name))

        custom_data = self._custom_map_to_sql(item, sql_data)
        if custom_data:
            sql_data = custom_data

        sql_data.pop("created_at", None)
        sql_data.pop("updated_at", None)

        for k, v in sql_data.items():
            if isinstance(v, (list, dict)):
                sql_data[k] = json.dumps(v, ensure_ascii=False)
            elif isinstance(v, bool):
                sql_data[k] = 1 if v else 0

        return sql_data
    def _map_sql_to_dataclass(self, row: Dict[str, Any]) -> Any:
        if not row: return None
        
        custom_obj = self._custom_map_from_sql(row)
        if custom_obj: return custom_obj

        args = {}
        datetime_fields = {"buy_date", "sell_date", "event_date", "date"}

        def format_date(field_name: str, val: Any) -> str:
            if not val: return ""
            with_time = field_name in datetime_fields
            if isinstance(val, datetime):
                return val.strftime("%d.%m.%Y %H:%M") if with_time else val.strftime("%d.%m.%Y")
            if isinstance(val, date):
                return val.strftime("%d.%m.%Y")
            if isinstance(val, str):
                s = val.strip()
                if not s: return ""
                if "." in s and len(s.split(".")) >= 3:
                    return s
                try:
                    if "T" in s or ":" in s:
                        dt = datetime.fromisoformat(s.replace("Z", ""))
                        return dt.strftime("%d.%m.%Y %H:%M") if with_time else dt.strftime("%d.%m.%Y")
                except ValueError: pass
                try:
                    d = date.fromisoformat(s[:10])
                    return d.strftime("%d.%m.%Y")
                except ValueError:
                    return s
            return str(val)

        from inspect import signature
        try:
            annotations = getattr(self.data_class, "__annotations__", {})
            def unwrap_optional(t):
                origin = get_origin(t)
                if origin is Union:
                    args_ = get_args(t)
                    if len(args_) == 2 and type(None) in args_:
                        return args_[0] if args_[1] is type(None) else args_[1]
                return t

            valid_keys = signature(self.data_class).parameters.keys()
            for k in valid_keys:
                if k in row:
                    val = row[k]
                    if "date" in k:
                        val = format_date(k, val)
                    target = unwrap_optional(annotations.get(k))
                    origin = get_origin(target)

                    if target is float:
                        if isinstance(val, Decimal): val = float(val)
                        elif isinstance(val, (int, str)):
                            try: val = float(val)
                            except Exception: pass
                    elif target is int:
                        if isinstance(val, Decimal): val = int(val)
                        elif isinstance(val, str):
                            try: val = int(float(val))
                            except Exception: pass
                    elif target is bool:
                        if isinstance(val, str):
                            val = val.strip() in {"1", "true", "True", "YES", "yes"}
                        else:
                            val = bool(val)
                    elif origin in (list, dict) and isinstance(val, str):
                        try: val = json.loads(val)
                        except Exception: pass
                        
                    args[k] = val

            if 'id' in row: args['id'] = row['id']
            if 'profile_id' in row: args['profile_id'] = row['profile_id']
            return self.data_class(**args)
            
        except Exception as e:
            logging.error(f"Generic Mapping error for {self.table_name}: {e} | Row: {row}")
            return None

    def _custom_map_to_sql(self, item: Any, current_sql_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        return current_sql_data

    def _custom_map_from_sql(self, row: Dict[str, Any]) -> Optional[Any]:
        return None

    def get_all(self) -> List[Any]:
        if getattr(self.data_manager, "is_aggregate", False):
            if not getattr(self.data_manager, "profile_ids", None):
                return []
            placeholders = ", ".join(["%s"] * len(self.data_manager.profile_ids))
            sql = f"SELECT * FROM {self.table_name} WHERE profile_id IN ({placeholders})"
            params = list(self.data_manager.profile_ids)
        else:
            sql = f"SELECT * FROM {self.table_name} WHERE profile_id = %s"
            params = [self.profile_id]

        if self.filter_account_id and self.table_name == "financial_transactions":
            sql += " AND account_id = %s"
            params.append(self.filter_account_id)

        tables_with_soft_delete = [
            "product_definitions", "stock_items", "financial_accounts",
            "financial_transactions", "sales", "debt_records",
            "repair_records", "scheduled_transactions", "persons", "depots", "users"
        ]
        
        if self.table_name in tables_with_soft_delete and self.db.has_column(self.table_name, "is_deleted"):
            sql += " AND is_deleted = 0"

        rows = self.db.fetch_all(sql, tuple(params))
        results = []
        for row in rows:
            obj = self._map_sql_to_dataclass(row)
            if obj:
                if getattr(self.data_manager, "is_aggregate", False) and hasattr(obj, 'profile_id'):
                    obj._branch_name = self.data_manager.profile_map.get(obj.profile_id, "Bilinmeyen Şube")
                results.append(obj)
        return results

    def save(self) -> None:
        pass

    def add_item(self, item: Any, cursor=None) -> None:
        if getattr(self.data_manager, "is_aggregate", False):
            raise PermissionError("👑 Patron Modunda (Read-Only) veri eklenemez/değiştirilemez!")
        sql_data = self._map_dataclass_to_sql_fields(item)
        columns = list(sql_data.keys())
        placeholders = ["%s"] * len(columns)
        values = list(sql_data.values())

        query = f"INSERT INTO {self.table_name} ({', '.join(columns)}) VALUES ({', '.join(placeholders)})"
        record_id = str(sql_data.get("id") or getattr(item, "id", "") or "")

        def _execute_with_cursor(cur) -> None:
            tx_uuid = str(uuid.uuid4())
            cur.execute(query, tuple(values))
            after_row = fetch_row_snapshot_by_id(
                db=self.db,
                table_name=self.table_name,
                record_id=record_id,
                profile_id=self.profile_id,
                cursor=cur,
            )
            queue_row_sync_event(
                db=self.db,
                table_name=self.table_name,
                profile_id=self.profile_id,
                action_type=SYNC_ACTION_ADD,
                record_id=record_id,
                before_row=None,
                after_row=after_row or dict(sql_data),
                cursor=cur,
                transaction_uuid=tx_uuid,
                meta={"source": "BaseManager.add_item"},
            )

        try:
            if cursor:
                _execute_with_cursor(cursor)
            else:
                with self.db.transaction_scope() as new_cursor:
                    _execute_with_cursor(new_cursor)
        except Exception as e:
            logger.error(f"Add Item Error in {self.table_name}: {e}")
            raise e

    def update_item(self, item_id: str, new_data: Dict[str, Any], cursor=None) -> bool:
        if getattr(self.data_manager, "is_aggregate", False):
            raise PermissionError("👑 Patron Modunda (Read-Only) veri güncellenemez!")
        local_item = self.find_by_id(item_id, cursor=cursor)
        if not local_item:
            logger.warning(f"Update failed: Item {item_id} not found in memory ({self.table_name}).")
            return False

        for key, value in new_data.items():
            if hasattr(local_item, key):
                setattr(local_item, key, value)

        if hasattr(local_item, 'updated_at'):
            local_item.updated_at = datetime.now().isoformat()

        sql_data = self._map_dataclass_to_sql_fields(local_item)

        if 'id' in sql_data: del sql_data['id']
        if 'profile_id' in sql_data: del sql_data['profile_id']

        safe_sql_data = {}
        for k, v in sql_data.items():
            if k.isidentifier(): safe_sql_data[k] = v

        set_clause = ", ".join([f"{col} = %s" for col in safe_sql_data.keys()])
        values = list(safe_sql_data.values())
        values.append(item_id)
        values.append(self.profile_id)

        query = f"UPDATE {self.table_name} SET {set_clause} WHERE id = %s AND profile_id = %s"

        def _execute_with_cursor(cur) -> bool:
            tx_uuid = str(uuid.uuid4())
            before_row = fetch_row_snapshot_by_id(
                db=self.db,
                table_name=self.table_name,
                record_id=item_id,
                profile_id=self.profile_id,
                cursor=cur,
            )

            cur.execute(query, tuple(values))
            changed = int(getattr(cur, "rowcount", 0) or 0) > 0
            if not changed:
                return False

            after_row = fetch_row_snapshot_by_id(
                db=self.db,
                table_name=self.table_name,
                record_id=item_id,
                profile_id=self.profile_id,
                cursor=cur,
            )
            queue_row_sync_event(
                db=self.db,
                table_name=self.table_name,
                profile_id=self.profile_id,
                action_type=SYNC_ACTION_UPDATE,
                record_id=item_id,
                before_row=before_row,
                after_row=after_row,
                cursor=cur,
                transaction_uuid=tx_uuid,
                meta={"source": "BaseManager.update_item"},
            )
            return True

        try:
            if cursor:
                return _execute_with_cursor(cursor)
            with self.db.transaction_scope() as new_cursor:
                return _execute_with_cursor(new_cursor)
        except Exception as e:
            logger.error(f"Update Item Error in {self.table_name}: {e}")
            return False

    def delete_item(self, item_id: str, cursor=None) -> bool:
        if getattr(self.data_manager, "is_aggregate", False):
            raise PermissionError("👑 Patron Modunda (Read-Only) veri silinemez!")
        tables_with_soft_delete = [
            "product_definitions", "stock_items", "financial_accounts",
            "financial_transactions", "sales", "debt_records",
            "repair_records", "scheduled_transactions", "persons", "depots"
        ]

        if self.table_name in tables_with_soft_delete and self.db.has_column(self.table_name, "is_deleted"):
            query = f"UPDATE {self.table_name} SET is_deleted = 1 WHERE id = %s AND profile_id = %s"
        else:
            query = f"DELETE FROM {self.table_name} WHERE id = %s AND profile_id = %s"

        def _execute_with_cursor(cur) -> bool:
            tx_uuid = str(uuid.uuid4())
            before_row = fetch_row_snapshot_by_id(
                db=self.db,
                table_name=self.table_name,
                record_id=item_id,
                profile_id=self.profile_id,
                cursor=cur,
            )

            cur.execute(query, (item_id, self.profile_id))
            changed = int(getattr(cur, "rowcount", 0) or 0) > 0
            if not changed:
                return False

            after_row = fetch_row_snapshot_by_id(
                db=self.db,
                table_name=self.table_name,
                record_id=item_id,
                profile_id=self.profile_id,
                cursor=cur,
            )
            queue_row_sync_event(
                db=self.db,
                table_name=self.table_name,
                profile_id=self.profile_id,
                action_type=SYNC_ACTION_DELETE,
                record_id=item_id,
                before_row=before_row,
                after_row=after_row,
                cursor=cur,
                transaction_uuid=tx_uuid,
                meta={"source": "BaseManager.delete_item"},
            )
            return True

        try:
            if cursor:
                return _execute_with_cursor(cursor)
            with self.db.transaction_scope() as new_cursor:
                return _execute_with_cursor(new_cursor)
        except Exception as e:
            logger.error(f"Delete Item Error in {self.table_name}: {e}")
            return False

    def find_by_id(self, item_id: str, cursor=None) -> Optional[Any]:
        sql = f"SELECT * FROM {self.table_name} WHERE id = %s AND profile_id = %s"
        if cursor:
            cursor.execute(sql, (item_id, self.profile_id))
            row = cursor.fetchone()
        else:
            row = self.db.fetch_one(sql, (item_id, self.profile_id))

        if row:
            return self._map_sql_to_dataclass(row)
        return None

    def find_by_attribute(self, attr_name: str, value: Any) -> Optional[Any]:
        col_name = attr_name
        if self.table_name == "product_definitions" and attr_name == "product_name":
            col_name = "name"
        elif self.table_name == "stock_items" and attr_name == "item_type":
            col_name = "item_type_name"

        sql = f"SELECT * FROM {self.table_name} WHERE {col_name} = %s AND profile_id = %s LIMIT 1"
        
        tables_with_soft_delete = ["product_definitions", "stock_items", "persons", "depots"]
        if self.table_name in tables_with_soft_delete and self.db.has_column(self.table_name, "is_deleted"):
            sql = sql.replace("LIMIT 1", "AND is_deleted = 0 LIMIT 1")
            
        row = self.db.fetch_one(sql, (value, self.profile_id))
        if row:
            return self._map_sql_to_dataclass(row)
        return None

class SettingsManager:
    def __init__(self, data_manager: DataManager):
        self.data_manager = data_manager
        self.db = data_manager.db
        self.profile_id = data_manager.profile_id
        self.settings: Dict[str, Any] = self._load_settings()

    def _load_settings(self) -> Dict[str, Any]:
        query = "SELECT setting_key, setting_value FROM app_settings WHERE profile_id = %s"
        rows = self.db.fetch_all(query, (self.profile_id,))
        settings = {}
        for r in rows:
            try: settings[r['setting_key']] = json.loads(r['setting_value'])
            except: settings[r['setting_key']] = r['setting_value']

        defaults = {
            "language": "TR", "display_currency": "TRY", "theme_name": "Green Harmony",
            "tab_position": "Sol", "password_enabled": False, "employees": {},
            "shop_name": "GOLDER KUYUMCU MUHASEBE PROGRAMI",
            "shop_phone": "",
            "company_name": "",
            "company_address": "",
            "tax_office": "",
            "tax_number": "",
            "company_phone": "",
            "company_email": "",
            "document_prefix": "DOC",
        }
        for k, v in defaults.items():
            if k not in settings: settings[k] = v
        return settings

    def get_setting(self, key: str, default: Any = None) -> Any:
        return self.settings.get(key, default)

    def set_setting(self, key: str, value: Any) -> None:
        self.settings[key] = value
        self.save_settings(key, value)

    def save_settings(self, specific_key: str = None, specific_value: Any = None) -> None:
        if specific_key:
            val_str = json.dumps(specific_value, ensure_ascii=False, cls=SyncJSONEncoder)
            s_type = type(specific_value).__name__

            with self.db.transaction_scope() as cursor:
                cursor.execute(
                    """
                    SELECT profile_id, setting_key, setting_value, setting_type
                    FROM app_settings
                    WHERE profile_id = %s AND setting_key = %s
                    LIMIT 1
                    """,
                    (self.profile_id, specific_key),
                )
                before_row = cursor.fetchone()

                cursor.execute(
                    """
                    INSERT INTO app_settings (profile_id, setting_key, setting_value, setting_type)
                    VALUES (%s, %s, %s, %s)
                    ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), setting_type = VALUES(setting_type)
                    """,
                    (self.profile_id, specific_key, val_str, s_type),
                )

                cursor.execute(
                    """
                    SELECT profile_id, setting_key, setting_value, setting_type
                    FROM app_settings
                    WHERE profile_id = %s AND setting_key = %s
                    LIMIT 1
                    """,
                    (self.profile_id, specific_key),
                )
                after_row = cursor.fetchone()

                queue_row_sync_event(
                    db=self.db,
                    table_name="app_settings",
                    profile_id=self.profile_id,
                    action_type=SYNC_ACTION_UPDATE if before_row else SYNC_ACTION_ADD,
                    record_id=setting_record_id(self.profile_id, specific_key),
                    before_row=dict(before_row) if before_row else None,
                    after_row=dict(after_row) if after_row else None,
                    cursor=cursor,
                    transaction_uuid=str(uuid.uuid4()),
                    meta={
                        "source": "SettingsManager.save_settings",
                        "setting_key": str(specific_key),
                    },
                )
        else:
            for k, v in self.settings.items():
                self.save_settings(k, v)

    def get_company_info(self) -> Dict[str, str]:
        def _pick(*keys: str, default: str = "") -> str:
            for key in keys:
                value = self.get_setting(key, "")
                text = str(value or "").strip()
                if text:
                    return text
            return default

        company_name = _pick("company_name", "company_title", "firma_unvani", "shop_name")
        company_phone = _pick("company_phone", "shop_phone")
        return {
            "company_name": company_name,
            "company_address": _pick("company_address", "firma_adresi", "shop_address"),
            "tax_office": _pick("tax_office", "vergi_dairesi"),
            "tax_number": _pick("tax_number", "vergi_no", "tax_no", "vergi_numarasi"),
            "company_phone": company_phone,
            "company_email": _pick("company_email", "email", "eposta"),
            "document_prefix": _pick("document_prefix", default="DOC") or "DOC",
        }

    def get_employees(self) -> Dict[str, Any]:
        return self.get_setting("employees", {})

    def add_employee(self, emp_id: str, data: Dict[str, Any]):
        emps = self.get_employees()
        emps[emp_id] = data
        self.set_setting("employees", emps)

    def delete_employee(self, emp_id: str):
        emps = self.get_employees()
        if emp_id in emps:
            del emps[emp_id]
            self.set_setting("employees", emps)

class StockMovementManager(BaseManager):
    def __init__(self, data_manager: DataManager):
        super().__init__(data_manager, "stock_movements", StockMovement)

    def add_log(self, item_type, product_name, op_type, qty, source, dest, desc, user="Admin", cursor=None):
        try:
            movement = StockMovement(
                date=datetime.now().isoformat(), item_type=item_type, product_name=product_name,
                operation_type=op_type, quantity=qty, source=source, destination=dest,
                description=desc, user=user)
            self.add_item(movement, cursor=cursor) 
        except Exception as e:
            logger.error(f"Stok hareketi loglanamadi: {e}")
            raise e


class AggregateDataManager(DataManager):
    """
    FAZ 6: Patron Modu için Çoklu Profil Yöneticisi
    Sorguları WHERE profile_id IN (X, Y, Z) formatına çevirir.
    """
    def __init__(self, profile_ids: List[str], profile_map: Dict[str, str]):
        from core.db_factory import get_database_adapter
        self.profile_name = "👑 Patron Görünümü (Tüm Şubeler)"
        self.db = get_database_adapter()
        self.profile_ids = profile_ids
        self.profile_map = profile_map
        self.is_aggregate = True
        self.profile_id = profile_ids[0] if profile_ids else ""  # Fallback




