
import hashlib
import hmac
import uuid
import logging
import json
import binascii
from pathlib import Path
from typing import Dict, Any, List, Optional

from PyQt6.QtWidgets import (
    QDialog, QVBoxLayout, QLabel, QComboBox, QLineEdit, QHBoxLayout,
    QPushButton, QMessageBox, QGroupBox, QFormLayout, QCheckBox,
    QScrollArea, QFrame, QWidget, QDialogButtonBox, QTreeWidget, QTreeWidgetItem,
    QInputDialog, QListWidget, QListWidgetItem, QFileDialog
)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QColor

from core.config import (
    APP_THEMES,
    SUB_TAB_PERMISSIONS,
    RESOURCES_DIR,
    load_language,
    get_app_data_directory,
    CUSTOM_THEMES_FILE,
)
from core.db import (
    SettingsManager,
    DataManager,
    fetch_row_snapshot_by_id,
    queue_row_sync_event,
    setting_record_id,
    SYNC_ACTION_ADD,
    SYNC_ACTION_DELETE,
    SYNC_ACTION_UPDATE,
)

logger = logging.getLogger(__name__)
_PROFILE_PASSWORD_SYNC_PUBLISHED: set[str] = set()
_USER_ACCESS_SYNC_PUBLISHED: set[str] = set()


def _load_custom_themes() -> Dict[str, Any]:
    path = Path(get_app_data_directory()) / CUSTOM_THEMES_FILE
    if not path.exists():
        return {}
    try:
        raw = json.loads(path.read_text(encoding="utf-8"))
    except Exception as exc:
        logger.error("Custom theme file could not be read: %s", exc)
        return {}
    return dict(raw) if isinstance(raw, dict) else {}

def _normalize_bool_setting(value: Any) -> bool:
    if value is None:
        return False
    if isinstance(value, bool):
        return value
    if isinstance(value, (int, float)):
        return value != 0
    s = str(value).strip()
    if not s:
        return False
    lower = s.lower()
    if lower in ("1", "true", "yes", "y", "on"):
        return True
    if lower in ("0", "false", "no", "n", "off", "none", "null", ""):
        return False
    try:
        parsed = json.loads(s)
        if isinstance(parsed, bool):
            return parsed
        if isinstance(parsed, (int, float)):
            return parsed != 0
    except Exception:
        pass
    return False

def _normalize_string_setting(value: Any) -> str:
    if value is None:
        return ""
    if isinstance(value, str):
        s = value.strip()
    else:
        s = str(value).strip()
    if not s:
        return ""
    if len(s) >= 2 and s[0] == '"' and s[-1] == '"':
        try:
            parsed = json.loads(s)
            if isinstance(parsed, str):
                return parsed.strip()
        except Exception:
            pass
    return s

class ValidationError(Exception):
    pass

class AuthRepository:
    def __init__(self, db, profile_id):
        self.db = db
        self.profile_id = profile_id
        self.ensure_default_admin()
        self._normalize_passwordless_admin_state()
        self._sync_admin_password_with_profile_password()
        self._publish_current_profile_password_settings_once()
        self._publish_existing_user_access_state_once()

    def log_audit(self, user_name: str, action: str, target_table: str, target_id: str, details: str = ""):
        try:
            sql = """
                INSERT INTO audit_logs (id, profile_id, user_name, action, target_table, target_id, details)
                VALUES (%s, %s, %s, %s, %s, %s, %s)
            """
            audit_id = str(uuid.uuid4())
            self.db.execute_non_query(sql, (
                audit_id, self.profile_id, user_name, action, target_table, target_id, details
            ))
        except Exception as e:
            logger.error(f"Audit log yazilamadi: {e}")

    def _hash_password(self, password: str, salt: bytes = None) -> str:
        import os
        if not salt:
            salt = os.urandom(16)
        pwd_hash = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)
        salt_hex = binascii.hexlify(salt).decode('ascii')
        hash_hex = binascii.hexlify(pwd_hash).decode('ascii')
        return f"{salt_hex}${hash_hex}"

    def ensure_default_admin(self):
        admin_row = self.db.fetch_one(
            """
            SELECT id
            FROM users
            WHERE profile_id = %s AND role = 'admin' AND is_deleted = 0
            ORDER BY created_at ASC
            LIMIT 1
            """,
            (self.profile_id,),
        )
        if not admin_row:
            admin_id = str(uuid.uuid4())
            with self.db.transaction_scope() as cursor:
                cursor.execute(
                    """
                    INSERT INTO users (id, profile_id, username, full_name, password_hash, role)
                    VALUES (%s, %s, %s, %s, %s, 'admin')
                    """,
                    (admin_id, self.profile_id, "admin", "Yönetici", ""),
                )
                cursor.execute(
                    "SELECT * FROM users WHERE id = %s AND profile_id = %s LIMIT 1",
                    (admin_id, self.profile_id),
                )
                after_row = cursor.fetchone()
                queue_row_sync_event(
                    db=self.db,
                    table_name="users",
                    profile_id=self.profile_id,
                    action_type=SYNC_ACTION_ADD,
                    record_id=admin_id,
                    before_row=None,
                    after_row=dict(after_row) if after_row else None,
                    cursor=cursor,
                    transaction_uuid=str(uuid.uuid4()),
                    meta={"source": "AuthRepository.ensure_default_admin"},
                )

    def _get_active_users(self, profile_id: Optional[str] = None) -> List[Dict[str, Any]]:
        pid = profile_id or self.profile_id
        rows = self.db.fetch_all(
            """
            SELECT id, username, full_name, role, password_hash
            FROM users
            WHERE profile_id = %s AND is_active = 1 AND is_deleted = 0
            ORDER BY
                CASE WHEN role = 'admin' THEN 0 ELSE 1 END,
                created_at ASC,
                full_name ASC
            """,
            (pid,),
        ) or []
        return [dict(row) for row in rows if isinstance(row, dict)]

    def _get_unique_active_users(self, profile_id: Optional[str] = None) -> List[Dict[str, Any]]:
        unique_users: List[Dict[str, Any]] = []
        seen_keys = set()
        for user in self._get_active_users(profile_id):
            username_key = str((user or {}).get("username") or "").strip().lower()
            role_key = str((user or {}).get("role") or "").strip().lower()
            dedupe_key = (username_key, role_key)
            if dedupe_key in seen_keys:
                continue
            seen_keys.add(dedupe_key)
            unique_users.append(user)
        return unique_users

    def _normalize_passwordless_admin_state(self, profile_id: Optional[str] = None) -> None:
        pid = profile_id or self.profile_id
        if self.has_admin_password(pid) or self._has_active_non_admin_users(pid):
            return
        active_users = self._get_unique_active_users(pid)
        if len(active_users) != 1:
            return
        only_user = active_users[0]
        if str(only_user.get("role") or "") != "admin":
            return
        if str(only_user.get("password_hash") or "").strip() == "":
            return
        try:
            with self.db.transaction_scope() as cursor:
                cursor.execute(
                    "SELECT * FROM users WHERE id = %s AND profile_id = %s LIMIT 1",
                    (only_user["id"], pid),
                )
                before_row = cursor.fetchone()
                cursor.execute(
                    "UPDATE users SET password_hash = %s WHERE id = %s AND profile_id = %s",
                    ("", only_user["id"], pid),
                )
                cursor.execute(
                    "SELECT * FROM users WHERE id = %s AND profile_id = %s LIMIT 1",
                    (only_user["id"], pid),
                )
                after_row = cursor.fetchone()
                queue_row_sync_event(
                    db=self.db,
                    table_name="users",
                    profile_id=pid,
                    action_type=SYNC_ACTION_UPDATE,
                    record_id=str(only_user["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": "AuthRepository._normalize_passwordless_admin_state"},
                )
        except Exception:
            pass

    def get_user_by_id(self, user_id: str) -> Optional[Dict[str, Any]]:
        sql = "SELECT * FROM users WHERE id = %s AND profile_id = %s AND is_deleted = 0"
        return self.db.fetch_one(sql, (user_id, self.profile_id))

    def hash_password(self, password: str) -> str:
        return self._hash_password(password)

    def get_user_by_username(self, username: str) -> Optional[Dict[str, Any]]:
        sql = "SELECT * FROM users WHERE username = %s AND profile_id = %s AND is_deleted = 0"
        return self.db.fetch_one(sql, (username, self.profile_id))

    def has_admin_password(self, profile_id: Optional[str] = None) -> bool:
        pid = profile_id or self.profile_id
        enabled, pwd_hash = self.get_profile_password_state(pid)
        return enabled and bool(pwd_hash)

    def _has_active_non_admin_users(self, profile_id: Optional[str] = None) -> bool:
        pid = profile_id or self.profile_id
        row = self.db.fetch_one(
            "SELECT id FROM users WHERE profile_id = %s AND role <> 'admin' AND is_active = 1 AND is_deleted = 0 LIMIT 1",
            (pid,),
        )
        return bool(row)

    def get_passwordless_bootstrap_user(self, profile_id: Optional[str] = None) -> Optional[Dict[str, Any]]:
        pid = profile_id or self.profile_id
        if self.has_admin_password(pid) or self._has_active_non_admin_users(pid):
            return None
        active_users = self._get_unique_active_users(pid)
        if len(active_users) != 1:
            return None
        user = active_users[0]
        if str(user.get("role") or "") != "admin":
            return None
        if str(user.get("password_hash") or "").strip() != "":
            return None
        return {
            "id": user["id"],
            "username": user["username"],
            "full_name": user.get("full_name") or user.get("username") or "Yönetici",
            "role": "admin",
        }

    def can_passwordless_login(self, user_id: str, profile_id: Optional[str] = None) -> bool:
        pid = profile_id or self.profile_id
        user = self.db.fetch_one(
            "SELECT id, role, password_hash FROM users WHERE id = %s AND profile_id = %s AND is_active = 1 AND is_deleted = 0 LIMIT 1",
            (user_id, pid),
        )
        if not user:
            return False
        if str(user.get("role") or "") != "admin":
            return False
        if self.has_admin_password(pid):
            return False
        if self._has_active_non_admin_users(pid):
            return False
        if str(user.get("password_hash") or "").strip() != "":
            return False
        return len(self._get_unique_active_users(pid)) == 1

    def _upsert_app_setting(self, profile_id: str, key: str, value: str, setting_type: str) -> None:
        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
                """,
                (profile_id, 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)
                """,
                (profile_id, key, value, setting_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
                """,
                (profile_id, key),
            )
            after_row = cursor.fetchone()

            queue_row_sync_event(
                db=self.db,
                table_name="app_settings",
                profile_id=profile_id,
                action_type=SYNC_ACTION_UPDATE if before_row else SYNC_ACTION_ADD,
                record_id=setting_record_id(profile_id, 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": "AuthRepository._upsert_app_setting",
                    "setting_key": str(key),
                },
            )

    def get_profile_password_state(self, profile_id: Optional[str] = None) -> tuple[bool, str]:
        pid = profile_id or self.profile_id
        rows = self.db.fetch_all(
            "SELECT setting_key, setting_value FROM app_settings WHERE profile_id = %s AND setting_key IN ('password_enabled', 'password_hash')",
            (pid,),
        ) or []
        enabled_raw = None
        hash_raw = ""
        for r in rows:
            k = r.get("setting_key")
            if k == "password_enabled":
                enabled_raw = r.get("setting_value")
            elif k == "password_hash":
                hash_raw = r.get("setting_value")
        return _normalize_bool_setting(enabled_raw), _normalize_string_setting(hash_raw)

    def set_profile_password(self, password_plain: str, profile_id: Optional[str] = None) -> None:
        pid = profile_id or self.profile_id
        new_hash = self._hash_password(password_plain)
        self._upsert_app_setting(pid, "password_enabled", "1", "bool")
        self._upsert_app_setting(pid, "password_hash", new_hash, "str")
        self._update_admin_users_password_hash(new_hash, pid)

    def clear_profile_password(self, profile_id: Optional[str] = None) -> None:
        pid = profile_id or self.profile_id
        self._upsert_app_setting(pid, "password_enabled", "0", "bool")
        self._upsert_app_setting(pid, "password_hash", "", "str")
        self._normalize_passwordless_admin_state(pid)

    def _update_admin_users_password_hash(self, password_hash: str, profile_id: Optional[str] = None) -> int:
        pid = profile_id or self.profile_id
        try:
            with self.db.transaction_scope() as cursor:
                cursor.execute(
                    "SELECT * FROM users WHERE profile_id = %s AND role = 'admin' AND is_deleted = 0",
                    (pid,),
                )
                before_rows = cursor.fetchall() or []
                before_map = {
                    str((row or {}).get("id") or ""): dict(row)
                    for row in before_rows
                    if (row or {}).get("id")
                }

                cursor.execute(
                    """
                    UPDATE users
                    SET password_hash = %s
                    WHERE profile_id = %s AND role = 'admin' AND is_deleted = 0
                    """,
                    (password_hash, pid),
                )

                if not before_map:
                    return 0

                cursor.execute(
                    "SELECT * FROM users WHERE profile_id = %s AND role = 'admin' AND is_deleted = 0",
                    (pid,),
                )
                after_rows = cursor.fetchall() or []
                changed = 0
                for row in after_rows:
                    user_id = str((row or {}).get("id") or "")
                    if not user_id:
                        continue
                    before_row = before_map.get(user_id)
                    after_row = dict(row)
                    if before_row == after_row:
                        continue
                    queue_row_sync_event(
                        db=self.db,
                        table_name="users",
                        profile_id=pid,
                        action_type=SYNC_ACTION_UPDATE,
                        record_id=user_id,
                        before_row=before_row,
                        after_row=after_row,
                        cursor=cursor,
                        transaction_uuid=str(uuid.uuid4()),
                        meta={"source": "AuthRepository._update_admin_users_password_hash"},
                    )
                    changed += 1
                return changed
        except Exception as e:
            return 0

    def _sync_admin_password_with_profile_password(self) -> None:
        try:
            enabled, profile_hash = self.get_profile_password_state(self.profile_id)
            if not enabled or not profile_hash:
                return
            rows = self.db.fetch_all(
                "SELECT id, password_hash FROM users WHERE profile_id = %s AND role = 'admin' AND is_deleted = 0",
                (self.profile_id,),
            ) or []
            if not rows:
                return
            if all(((r or {}).get("password_hash") or "") == profile_hash for r in rows):
                return
            self._update_admin_users_password_hash(profile_hash, self.profile_id)
        except Exception:
            pass

    def _publish_current_profile_password_settings_once(self) -> None:
        try:
            enabled, profile_hash = self.get_profile_password_state(self.profile_id)
            cache_key = f"{self.profile_id}:{int(bool(enabled))}:{profile_hash}"
            if cache_key in _PROFILE_PASSWORD_SYNC_PUBLISHED:
                return

            rows = self.db.fetch_all(
                """
                SELECT profile_id, setting_key, setting_value, setting_type
                FROM app_settings
                WHERE profile_id = %s AND setting_key IN ('password_enabled', 'password_hash')
                """,
                (self.profile_id,),
            ) or []
            if not rows:
                return

            for row in rows:
                row_dict = dict(row)
                setting_key = str(row_dict.get("setting_key") or "").strip()
                if not setting_key:
                    continue
                queue_row_sync_event(
                    db=self.db,
                    table_name="app_settings",
                    profile_id=self.profile_id,
                    action_type=SYNC_ACTION_UPDATE,
                    record_id=setting_record_id(self.profile_id, setting_key),
                    before_row=None,
                    after_row=row_dict,
                    transaction_uuid=str(uuid.uuid4()),
                    meta={"source": "AuthRepository._publish_current_profile_password_settings_once"},
                )

            _PROFILE_PASSWORD_SYNC_PUBLISHED.add(cache_key)
        except Exception:
            pass

    def _publish_existing_user_access_state_once(self) -> None:
        try:
            cache_key = str(self.profile_id or "").strip()
            if not cache_key or cache_key in _USER_ACCESS_SYNC_PUBLISHED:
                return

            permission_rows = self.db.fetch_all(
                """
                SELECT up.user_id, up.permission_code
                FROM user_permissions up
                INNER JOIN users u ON u.id = up.user_id
                WHERE u.profile_id = %s AND u.is_deleted = 0
                ORDER BY up.user_id ASC, up.permission_code ASC
                """,
                (self.profile_id,),
            ) or []
            for row in permission_rows:
                row_dict = dict(row)
                user_id = str(row_dict.get("user_id") or "").strip()
                perm_code = str(row_dict.get("permission_code") or "").strip()
                if not user_id or not perm_code:
                    continue
                queue_row_sync_event(
                    db=self.db,
                    table_name="user_permissions",
                    profile_id=self.profile_id,
                    action_type=SYNC_ACTION_ADD,
                    record_id=self._permission_record_id(user_id, perm_code),
                    before_row=None,
                    after_row=row_dict,
                    transaction_uuid=str(uuid.uuid4()),
                    meta={"source": "AuthRepository._publish_existing_user_access_state_once"},
                )

            scope_rows = self.db.fetch_all(
                """
                SELECT uas.user_id, uas.scope_type, uas.target_id
                FROM user_access_scopes uas
                INNER JOIN users u ON u.id = uas.user_id
                WHERE u.profile_id = %s AND u.is_deleted = 0
                ORDER BY uas.user_id ASC, uas.scope_type ASC, uas.target_id ASC
                """,
                (self.profile_id,),
            ) or []
            for row in scope_rows:
                row_dict = dict(row)
                user_id = str(row_dict.get("user_id") or "").strip()
                scope_type = str(row_dict.get("scope_type") or "").strip()
                target_id = str(row_dict.get("target_id") or "").strip()
                if not user_id or not scope_type or not target_id:
                    continue
                queue_row_sync_event(
                    db=self.db,
                    table_name="user_access_scopes",
                    profile_id=self.profile_id,
                    action_type=SYNC_ACTION_ADD,
                    record_id=self._scope_record_id(user_id, scope_type, target_id),
                    before_row=None,
                    after_row=row_dict,
                    transaction_uuid=str(uuid.uuid4()),
                    meta={"source": "AuthRepository._publish_existing_user_access_state_once"},
                )

            _USER_ACCESS_SYNC_PUBLISHED.add(cache_key)
        except Exception:
            pass

    def _verify_pbkdf2_hash(self, password_input: str, stored_hash: str) -> bool:
        if not stored_hash or "$" not in stored_hash:
            return False
        try:
            salt_hex, hash_hex = stored_hash.split("$", 1)
            salt = binascii.unhexlify(salt_hex)
            expected = binascii.unhexlify(hash_hex)
            check = hashlib.pbkdf2_hmac("sha256", password_input.encode("utf-8"), salt, 100000)
            return hmac.compare_digest(check, expected)
        except Exception:
            return False

    def verify_profile_password(self, password_input: str, profile_id: Optional[str] = None, auto_upgrade_md5: bool = True) -> bool:
        pid = profile_id or self.profile_id
        enabled, stored_hash = self.get_profile_password_state(pid)
        if not enabled:
            return True
        stored_hash = _normalize_string_setting(stored_hash)
        if not stored_hash:
            return False
        if len(stored_hash) == 32 and "$" not in stored_hash:
            if hashlib.md5(password_input.encode()).hexdigest() == stored_hash:
                if auto_upgrade_md5:
                    self.set_profile_password(password_input, profile_id=pid)
                return True
            return False
        return self._verify_pbkdf2_hash(password_input, stored_hash)

    def get_all_users(self, active_only: bool = True) -> List[Dict[str, Any]]:
        sql = "SELECT id, username, full_name, role FROM users WHERE profile_id = %s AND is_deleted = 0"
        params = [self.profile_id]
        if active_only:
            sql += " AND is_active = 1"
        sql += " ORDER BY full_name ASC"
        return self.db.fetch_all(sql, tuple(params))

    def verify_credentials(self, user_id: str, password_input: str) -> bool:
        sql = "SELECT password_hash FROM users WHERE id = %s AND profile_id = %s AND is_deleted = 0 AND is_active = 1"
        user = self.db.fetch_one(sql, (user_id, self.profile_id))
        if not user:
            return False
        password_input = str(password_input or "")
        if password_input == "" and self.can_passwordless_login(user_id, self.profile_id):
            return True
        stored_hash = (user.get('password_hash') or "").strip()
        if not stored_hash:
            return False
        if len(stored_hash) == 32 and '$' not in stored_hash:
            if hashlib.md5(password_input.encode()).hexdigest() == stored_hash:
                new_secure_hash = self._hash_password(password_input)
                self.db.execute_non_query(
                    "UPDATE users SET password_hash=%s WHERE id=%s",
                    (new_secure_hash, user_id)
                )
                return True
            return False
        try:
            if '$' not in stored_hash: return False
            salt_hex, hash_hex = stored_hash.split('$')
            salt = binascii.unhexlify(salt_hex)
            expected = binascii.unhexlify(hash_hex)
            check_hash = hashlib.pbkdf2_hmac('sha256', password_input.encode('utf-8'), salt, 100000)
            return hmac.compare_digest(check_hash, expected)
        except Exception:
            return False

    def get_user_permissions(self, user_id: str) -> List[str]:
        sql = "SELECT permission_code FROM user_permissions WHERE user_id = %s"
        rows = self.db.fetch_all(sql, (user_id,))
        return [row['permission_code'] for row in rows]

    def get_access_scopes(self, user_id: str, scope_type: str) -> List[str]:
        sql = "SELECT target_id FROM user_access_scopes WHERE user_id = %s AND scope_type = %s"
        rows = self.db.fetch_all(sql, (user_id, scope_type))
        return [row['target_id'] for row in rows]

    def _permission_record_id(self, user_id: str, permission_code: str) -> str:
        return setting_record_id(user_id, f"perm:{permission_code}")

    def _scope_record_id(self, user_id: str, scope_type: str, target_id: str) -> str:
        return setting_record_id(user_id, f"scope:{scope_type}:{target_id}")

    def _fetch_user_permissions_rows(self, cursor, user_id: str) -> List[Dict[str, Any]]:
        cursor.execute(
            """
            SELECT user_id, permission_code
            FROM user_permissions
            WHERE user_id = %s
            ORDER BY permission_code ASC
            """,
            (user_id,),
        )
        return [dict(row) for row in (cursor.fetchall() or [])]

    def _fetch_user_scope_rows(self, cursor, user_id: str) -> List[Dict[str, Any]]:
        cursor.execute(
            """
            SELECT user_id, scope_type, target_id
            FROM user_access_scopes
            WHERE user_id = %s
            ORDER BY scope_type ASC, target_id ASC
            """,
            (user_id,),
        )
        return [dict(row) for row in (cursor.fetchall() or [])]

    def create_staff(self, user_data: Dict[str, Any]) -> str:
        if not self.has_admin_password(self.profile_id):
            raise ValidationError("Cannot create staff without profile password")
        staff_data = dict(user_data or {})
        staff_data["role"] = "employee"
        return self.create_user(staff_data)

    def create_user(self, user_data: Dict[str, Any]) -> str:
        user_id = str(uuid.uuid4())
        role = user_data.get('role', 'employee')
        if role not in ['admin', 'employee']:
            role = 'employee'
        try:
            with self.db.transaction_scope() as cursor:
                tx_uuid = str(uuid.uuid4())
                sql_user = """
                    INSERT INTO users (id, profile_id, username, full_name, password_hash, role)
                    VALUES (%s, %s, %s, %s, %s, %s)
                """
                cursor.execute(sql_user, (
                    user_id, self.profile_id, user_data['username'],
                    user_data['name'], user_data['password_hash'], role
                ))
                user_row = fetch_row_snapshot_by_id(self.db, "users", user_id, self.profile_id, cursor)
                queue_row_sync_event(
                    self.db,
                    "users",
                    self.profile_id,
                    SYNC_ACTION_ADD,
                    user_id,
                    None,
                    user_row,
                    cursor,
                    transaction_uuid=tx_uuid,
                    meta={"source": "create_user"},
                )

                for perm in dict.fromkeys(user_data.get('permissions', []) or []):
                    perm = str(perm or "").strip()
                    if not perm:
                        continue
                    cursor.execute(
                        "INSERT INTO user_permissions (user_id, permission_code) VALUES (%s, %s)",
                        (user_id, perm),
                    )
                    queue_row_sync_event(
                        self.db,
                        "user_permissions",
                        self.profile_id,
                        SYNC_ACTION_ADD,
                        self._permission_record_id(user_id, perm),
                        None,
                        {"user_id": user_id, "permission_code": perm},
                        cursor,
                        transaction_uuid=tx_uuid,
                        meta={"source": "create_user"},
                    )

                self._insert_scopes(
                    cursor,
                    user_id,
                    'depot',
                    user_data.get('allowed_depots', []),
                    self.profile_id,
                    tx_uuid,
                    meta_source="create_user",
                )
                self._insert_scopes(
                    cursor,
                    user_id,
                    'cash_account',
                    user_data.get('allowed_cash_accounts', []),
                    self.profile_id,
                    tx_uuid,
                    meta_source="create_user",
                )
                self._insert_scopes(
                    cursor,
                    user_id,
                    'card_account',
                    user_data.get('allowed_card_accounts', []),
                    self.profile_id,
                    tx_uuid,
                    meta_source="create_user",
                )
            return user_id
        except Exception as e:
            logger.error(f"Kullanici olusturma hatasi: {e}")
            raise e

    def update_user(self, user_id: str, user_data: Dict[str, Any]):
        try:
            with self.db.transaction_scope() as cursor:
                tx_uuid = str(uuid.uuid4())
                before_user = fetch_row_snapshot_by_id(self.db, "users", user_id, self.profile_id, cursor)
                if not before_user:
                    raise ValidationError("Kullanici bulunamadi")

                before_permissions = self._fetch_user_permissions_rows(cursor, user_id)
                before_scopes = self._fetch_user_scope_rows(cursor, user_id)

                if user_data.get('password_hash'):
                    cursor.execute(
                        "UPDATE users SET full_name=%s, password_hash=%s WHERE id=%s AND profile_id=%s",
                        (user_data['name'], user_data['password_hash'], user_id, self.profile_id),
                    )
                else:
                    cursor.execute(
                        "UPDATE users SET full_name=%s WHERE id=%s AND profile_id=%s",
                        (user_data['name'], user_id, self.profile_id),
                    )

                after_user = fetch_row_snapshot_by_id(self.db, "users", user_id, self.profile_id, cursor)
                if before_user != after_user:
                    queue_row_sync_event(
                        self.db,
                        "users",
                        self.profile_id,
                        SYNC_ACTION_UPDATE,
                        user_id,
                        before_user,
                        after_user,
                        cursor,
                        transaction_uuid=tx_uuid,
                        meta={"source": "update_user"},
                    )

                cursor.execute("DELETE FROM user_permissions WHERE user_id=%s", (user_id,))
                for before_row in before_permissions:
                    perm_code = str(before_row.get("permission_code") or "").strip()
                    if not perm_code:
                        continue
                    queue_row_sync_event(
                        self.db,
                        "user_permissions",
                        self.profile_id,
                        SYNC_ACTION_DELETE,
                        self._permission_record_id(user_id, perm_code),
                        before_row,
                        None,
                        cursor,
                        transaction_uuid=tx_uuid,
                        meta={"source": "update_user"},
                    )
                for perm in dict.fromkeys(user_data.get('permissions', []) or []):
                    perm = str(perm or "").strip()
                    if not perm:
                        continue
                    cursor.execute("INSERT INTO user_permissions (user_id, permission_code) VALUES (%s, %s)", (user_id, perm))
                    queue_row_sync_event(
                        self.db,
                        "user_permissions",
                        self.profile_id,
                        SYNC_ACTION_ADD,
                        self._permission_record_id(user_id, perm),
                        None,
                        {"user_id": user_id, "permission_code": perm},
                        cursor,
                        transaction_uuid=tx_uuid,
                        meta={"source": "update_user"},
                    )

                cursor.execute("DELETE FROM user_access_scopes WHERE user_id=%s", (user_id,))
                for before_row in before_scopes:
                    scope_type = str(before_row.get("scope_type") or "").strip()
                    target_id = str(before_row.get("target_id") or "").strip()
                    if not scope_type or not target_id:
                        continue
                    queue_row_sync_event(
                        self.db,
                        "user_access_scopes",
                        self.profile_id,
                        SYNC_ACTION_DELETE,
                        self._scope_record_id(user_id, scope_type, target_id),
                        before_row,
                        None,
                        cursor,
                        transaction_uuid=tx_uuid,
                        meta={"source": "update_user"},
                    )

                self._insert_scopes(
                    cursor,
                    user_id,
                    'depot',
                    user_data.get('allowed_depots', []),
                    self.profile_id,
                    tx_uuid,
                    meta_source="update_user",
                )
                self._insert_scopes(
                    cursor,
                    user_id,
                    'cash_account',
                    user_data.get('allowed_cash_accounts', []),
                    self.profile_id,
                    tx_uuid,
                    meta_source="update_user",
                )
                self._insert_scopes(
                    cursor,
                    user_id,
                    'card_account',
                    user_data.get('allowed_card_accounts', []),
                    self.profile_id,
                    tx_uuid,
                    meta_source="update_user",
                )
        except Exception as e:
            logger.error(f"Kullanici guncelleme hatasi: {e}")
            raise e

    def delete_user(self, user_id: str):
        try:
            with self.db.transaction_scope() as cursor:
                tx_uuid = str(uuid.uuid4())
                before_user = fetch_row_snapshot_by_id(self.db, "users", user_id, self.profile_id, cursor)
                if not before_user:
                    return
                before_permissions = self._fetch_user_permissions_rows(cursor, user_id)
                before_scopes = self._fetch_user_scope_rows(cursor, user_id)

                cursor.execute(
                    "UPDATE users SET is_deleted=1, is_active=0 WHERE id=%s AND profile_id=%s",
                    (user_id, self.profile_id),
                )
                after_user = fetch_row_snapshot_by_id(self.db, "users", user_id, self.profile_id, cursor)
                queue_row_sync_event(
                    self.db,
                    "users",
                    self.profile_id,
                    SYNC_ACTION_UPDATE,
                    user_id,
                    before_user,
                    after_user,
                    cursor,
                    transaction_uuid=tx_uuid,
                    meta={"source": "delete_user"},
                )

                cursor.execute("DELETE FROM user_permissions WHERE user_id=%s", (user_id,))
                for before_row in before_permissions:
                    perm_code = str(before_row.get("permission_code") or "").strip()
                    if not perm_code:
                        continue
                    queue_row_sync_event(
                        self.db,
                        "user_permissions",
                        self.profile_id,
                        SYNC_ACTION_DELETE,
                        self._permission_record_id(user_id, perm_code),
                        before_row,
                        None,
                        cursor,
                        transaction_uuid=tx_uuid,
                        meta={"source": "delete_user"},
                    )

                cursor.execute("DELETE FROM user_access_scopes WHERE user_id=%s", (user_id,))
                for before_row in before_scopes:
                    scope_type = str(before_row.get("scope_type") or "").strip()
                    target_id = str(before_row.get("target_id") or "").strip()
                    if not scope_type or not target_id:
                        continue
                    queue_row_sync_event(
                        self.db,
                        "user_access_scopes",
                        self.profile_id,
                        SYNC_ACTION_DELETE,
                        self._scope_record_id(user_id, scope_type, target_id),
                        before_row,
                        None,
                        cursor,
                        transaction_uuid=tx_uuid,
                        meta={"source": "delete_user"},
                    )
        except Exception as e:
            logger.error(f"Kullanici silme hatasi: {e}")
            raise e

    def _insert_scopes(self, cursor, user_id, scope_type, items, profile_id, transaction_uuid, meta_source="user_scope"):
        for target in dict.fromkeys(items or []):
            target_id = str(target or "").strip()
            if not target_id:
                continue
            cursor.execute(
                "INSERT INTO user_access_scopes (user_id, scope_type, target_id) VALUES (%s, %s, %s)",
                (user_id, scope_type, target_id)
            )
            queue_row_sync_event(
                self.db,
                "user_access_scopes",
                profile_id,
                SYNC_ACTION_ADD,
                self._scope_record_id(user_id, scope_type, target_id),
                None,
                {"user_id": user_id, "scope_type": scope_type, "target_id": target_id},
                cursor,
                transaction_uuid=transaction_uuid,
                meta={"source": meta_source},
            )
class UserLoginDialog(QDialog):
    def __init__(self, settings_manager: SettingsManager, lang: Dict[str, str], parent=None):
        super().__init__(parent)
        self.sm = settings_manager
        self.db = settings_manager.db
        self.profile_id = settings_manager.profile_id
        self.auth_repo = AuthRepository(self.db, self.profile_id)
        self.lang = lang
        
        self.selected_user_role = None
        self.selected_user_permissions = None
        self.selected_user_name = None
        self.selected_allowed_depots = []
        self.selected_allowed_cash = []
        self.selected_allowed_card = []
        
        self.setWindowTitle(self.lang.get("user_login", "User Login"))
        self.setMinimumWidth(520)
        self.setMinimumHeight(260)
        self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowCloseButtonHint)
        
        layout = QVBoxLayout(self)
        layout.setContentsMargins(20, 20, 20, 20)
        
        card = QFrame()
        card.setObjectName("loginCard")
        card_layout = QVBoxLayout(card)
        card_layout.setContentsMargins(22, 22, 22, 22)
        
        title = QLabel(self.lang.get("user_login", "User Login"))
        title.setObjectName("loginTitle")
        card_layout.addWidget(title)
        
        hint = QLabel(self.lang.get("login_hint", "Select a user. If there is no staff and profile password is off, it can be left blank."))
        hint.setObjectName("loginHint")
        hint.setWordWrap(True)
        card_layout.addWidget(hint)
        
        form = QFormLayout()
        form.setLabelAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
        form.setFormAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
        form.setHorizontalSpacing(14)
        form.setVerticalSpacing(12)
        self.user_combo = QComboBox()
        users = self.auth_repo.get_all_users()
        unique_users = []
        seen_user_keys = set()
        for user in users:
            username_key = str((user or {}).get("username") or "").strip().lower()
            role_key = str((user or {}).get("role") or "").strip().lower()
            dedupe_key = (username_key, role_key)
            if dedupe_key in seen_user_keys:
                continue
            seen_user_keys.add(dedupe_key)
            unique_users.append(user)
        admins = [u for u in unique_users if u["role"] == "admin"]
        employees = [u for u in unique_users if u["role"] != "admin"]
        
        for user in admins:
            display_name = str(user.get("full_name") or user.get("username") or "Yönetici")
            self.user_combo.addItem(f"{display_name} ({self.lang.get('role_admin', 'Admin')})", user["id"])
        if admins and employees:
            self.user_combo.insertSeparator(self.user_combo.count())
        for user in employees:
            display_name = str(user.get("full_name") or user.get("username") or "")
            self.user_combo.addItem(f"{display_name} ({self.lang.get('role_employee', 'Staff')})", user["id"])
            
        self.pass_edit = QLineEdit()
        self.pass_edit.setEchoMode(QLineEdit.EchoMode.Password)
        self.pass_edit.setPlaceholderText(self.lang.get("password_optional_ph", "Password (leave blank if not needed)"))
        self.pass_edit.returnPressed.connect(self.verify_and_accept)
        self.user_combo.setMinimumHeight(46)
        self.pass_edit.setMinimumHeight(46)
        
        form.addRow(self.lang.get("user", "User"), self.user_combo)
        form.addRow(self.lang.get("password", "Password"), self.pass_edit)
        card_layout.addLayout(form)
        
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        login_btn = QPushButton(self.lang.get("login_btn", "Login"))
        login_btn.setObjectName("primaryButton")
        login_btn.setDefault(True)
        login_btn.clicked.connect(self.verify_and_accept)
        cancel_btn = QPushButton(self.lang.get("cancel", "Cancel"))
        cancel_btn.setObjectName("secondaryButton")
        cancel_btn.setMinimumHeight(40)
        cancel_btn.clicked.connect(self.reject)
        login_btn.setMinimumHeight(40)
        btn_layout.addWidget(cancel_btn)
        btn_layout.addWidget(login_btn)
        card_layout.addLayout(btn_layout)
        
        layout.addWidget(card)
        self._apply_styles()
        self.pass_edit.setFocus()

    def _apply_styles(self) -> None:
        theme_name = str(self.sm.get_setting("theme_name", "Green Harmony") or "Green Harmony").strip()
        all_themes = {**APP_THEMES, **_load_custom_themes()}
        colors = dict(all_themes.get(theme_name, APP_THEMES["Green Harmony"]) or {})

        primary = QColor(colors.get("primary", "#285430"))
        secondary = QColor(colors.get("secondary", "#5F8D4E"))
        background = QColor(colors.get("background", "#F1F8E9"))
        text_light = QColor(colors.get("text_light", "#FFFFFF"))
        danger = QColor(colors.get("danger", "#D32F2F"))

        dialog_bg = background.darker(170) if background.lightness() > 120 else background.lighter(115)
        card_start = primary.darker(185) if primary.lightness() > 120 else primary.darker(120)
        card_end = secondary.darker(165) if secondary.lightness() > 120 else secondary.darker(120)
        field_bg = secondary.darker(210) if secondary.lightness() > 120 else secondary.lighter(110)
        field_border = secondary.lighter(145) if secondary.lightness() < 140 else secondary.darker(105)
        muted_text = text_light.lighter(115)
        button_secondary_bg = background.darker(145) if background.lightness() > 120 else background.lighter(120)
        button_secondary_hover = background.darker(160) if background.lightness() > 120 else background.lighter(135)

        self.setStyleSheet(f"""
            QDialog {{
                background-color: {dialog_bg.name()};
                color: {text_light.name()};
            }}
            QFrame#loginCard {{
                background: qlineargradient(
                    x1:0, y1:0, x2:1, y2:1,
                    stop:0 {card_start.name()},
                    stop:1 {card_end.name()}
                );
                border: 1px solid {field_border.name()};
                border-radius: 18px;
            }}
            QLabel {{
                color: {text_light.name()};
                font-size: 13px;
                background: transparent;
            }}
            QLabel#loginTitle {{
                font-size: 24px;
                font-weight: 700;
                color: {text_light.name()};
                margin-bottom: 4px;
            }}
            QLabel#loginHint {{
                color: {muted_text.name()};
                font-size: 13px;
                line-height: 1.35;
                margin-bottom: 10px;
            }}
            QComboBox, QLineEdit {{
                border: 1px solid {field_border.name()};
                border-radius: 12px;
                padding: 10px 12px;
                min-height: 24px;
                background: {field_bg.name()};
                color: {text_light.name()};
                selection-background-color: {secondary.name()};
                selection-color: {text_light.name()};
            }}
            QComboBox:focus, QLineEdit:focus {{
                border: 1px solid {text_light.name()};
                background: {card_start.lighter(115).name()};
            }}
            QLineEdit::placeholder {{
                color: {muted_text.name()};
            }}
            QComboBox::drop-down {{
                border: 0;
                width: 28px;
            }}
            QComboBox QAbstractItemView {{
                background-color: {field_bg.name()};
                color: {text_light.name()};
                selection-background-color: {secondary.name()};
                selection-color: {text_light.name()};
                padding: 6px;
                outline: 0;
                border: 1px solid {field_border.name()};
            }}
            QPushButton {{
                border-radius: 10px;
                padding: 10px 16px;
                border: 1px solid transparent;
                color: {text_light.name()};
            }}
            QPushButton#primaryButton {{
                background-color: {primary.name()};
                color: {text_light.name()};
                font-weight: bold;
            }}
            QPushButton#primaryButton:hover {{
                background-color: {secondary.name()};
            }}
            QPushButton#secondaryButton {{
                background-color: {button_secondary_bg.name()};
                color: {text_light.name()};
                border-color: {field_border.name()};
            }}
            QPushButton#secondaryButton:hover {{
                background-color: {button_secondary_hover.name()};
            }}
            QPushButton:pressed {{
                background-color: {danger.name()};
            }}
        """)

    def verify_and_accept(self):
        user_id = self.user_combo.currentData()
        password = self.pass_edit.text()
        
        if not user_id:
            QMessageBox.warning(self, "Hata", "Kullanici secilmedi.")
            return
            
        if self.auth_repo.verify_credentials(user_id, password):
            user_info = self.auth_repo.get_user_by_id(user_id)
            if not user_info:
                return
            self.selected_user_role = user_info['role']
            self.selected_user_name = user_info['full_name']
            self.selected_username = user_info['username']
            
            if self.selected_user_role == 'admin' and self.auth_repo.has_admin_password(self.profile_id):
                if not self.auth_repo.verify_profile_password(password, profile_id=self.profile_id, auto_upgrade_md5=True):
                    QMessageBox.warning(self, "Hata", "Sifre yanlis!")
                    self.pass_edit.clear()
                    self.pass_edit.setFocus()
                    return
                    
            if self.selected_user_role == 'admin':
                self.selected_user_permissions = "ALL"
                self.selected_allowed_depots = None
                self.selected_allowed_cash = None
                self.selected_allowed_card = None
            else:
                self.selected_user_permissions = self.auth_repo.get_user_permissions(user_id)
                self.selected_allowed_depots = self.auth_repo.get_access_scopes(user_id, 'depot')
                self.selected_allowed_cash = self.auth_repo.get_access_scopes(user_id, 'cash_account')
                self.selected_allowed_card = self.auth_repo.get_access_scopes(user_id, 'card_account')
                
            self.accept()
        else:
            QMessageBox.warning(self, "Hata", "Sifre yanlis!")
            self.pass_edit.clear()
            self.pass_edit.setFocus()

class EmployeeEditorDialog(QDialog):
    def __init__(self, lang: Dict[str, str], depot_manager, cash_account_manager, card_account_manager, employee_data=None, parent=None):
        super().__init__(parent)
        self.lang = lang
        self.depot_manager = depot_manager
        self.cash_account_manager = cash_account_manager
        self.card_account_manager = card_account_manager
        self.employee_data = employee_data 
        
        self.setWindowTitle(self.lang.get("emp_management_title", "Staff Management"))
        self.setMinimumWidth(800)
        self.setMinimumHeight(600)
        
        layout = QVBoxLayout(self)
        form_group = QGroupBox(self.lang.get("emp_personal_info", "Personal Information"))
        form_layout = QFormLayout(form_group)
        self.name_edit = QLineEdit(employee_data['full_name'] if employee_data else "")
        self.pass_edit = QLineEdit()
        self.pass_edit.setEchoMode(QLineEdit.EchoMode.Password)
        
        placeholder = self.lang.get("emp_password_edit_ph", "Type to change") if employee_data else self.lang.get("emp_password_ph", "Password")
        self.pass_edit.setPlaceholderText(placeholder)
        
        form_layout.addRow(self.lang.get("emp_name", "Staff Name:"), self.name_edit)
        form_layout.addRow(self.lang.get("emp_password", "Password:"), self.pass_edit)
        layout.addWidget(form_group)
        
        perms_layout = QHBoxLayout()
        tab_group = QGroupBox(self.lang.get("emp_accessible_tabs", "Accessible Sub-Tabs"))
        tab_inner_layout = QVBoxLayout(tab_group)
        self.tree = QTreeWidget()
        self.tree.setHeaderLabel(self.lang.get("tabs_header", "Tabs"))
        
        existing_perms = employee_data.get('permissions', []) if employee_data else []
        for category_key, sub_tabs in SUB_TAB_PERMISSIONS.items():
            cat_item = QTreeWidgetItem(self.tree)
            
            # Kategori ismini lang üzerinden çevir, bulamazsa kendi key'ini kullan
            cat_item.setText(0, self.lang.get(category_key, category_key))
            
            cat_item.setFlags(cat_item.flags() | Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsAutoTristate)
            cat_item.setCheckState(0, Qt.CheckState.Unchecked)
            cat_item.setExpanded(True)
            for perm_id, default_perm_name in sub_tabs.items():
                child = QTreeWidgetItem(cat_item)
                
                # İzin isimlerini perm_id ile lang dosyasında ara, bulamazsa varsayılan Türkçe metni kullan
                translated_perm_name = self.lang.get(perm_id, default_perm_name)
                child.setText(0, translated_perm_name)
                child.setData(0, Qt.ItemDataRole.UserRole, perm_id)
                child.setFlags(child.flags() | Qt.ItemFlag.ItemIsUserCheckable)
                if perm_id in existing_perms:
                    child.setCheckState(0, Qt.CheckState.Checked)
                else:
                    child.setCheckState(0, Qt.CheckState.Unchecked)
                    
        tab_inner_layout.addWidget(self.tree)
        perms_layout.addWidget(tab_group, 1)
        
        access_scroll = QScrollArea()
        access_scroll.setWidgetResizable(True)
        access_content = QWidget()
        access_layout = QVBoxLayout(access_content)
        
        def create_scope_group(title, items, existing_ids):
            group = QGroupBox(title)
            gl = QVBoxLayout(group)
            checks = {}
            for item in items:
                i_id = getattr(item, 'id', str(item))
                i_name = getattr(item, 'name', str(item))
                chk = QCheckBox(i_name)
                if not employee_data or i_id in existing_ids:
                    chk.setChecked(True)
                checks[i_id] = chk
                gl.addWidget(chk)
            return group, checks
            
        depots = self.depot_manager.get_all()
        ex_depots = employee_data.get('allowed_depots', []) if employee_data else []
        depot_group, self.depot_checks = create_scope_group(self.lang.get("emp_depot_access", "Depot Access"), depots, ex_depots)
        access_layout.addWidget(depot_group)
        
        cash_accs = self.cash_account_manager.get_all()
        ex_cash = employee_data.get('allowed_cash_accounts', []) if employee_data else []
        cash_group, self.cash_checks = create_scope_group(self.lang.get("emp_cash_access", "Cash Account Access"), cash_accs, ex_cash)
        access_layout.addWidget(cash_group)
        
        card_accs = self.card_account_manager.get_all()
        ex_card = employee_data.get('allowed_card_accounts', []) if employee_data else []
        card_group, self.card_checks = create_scope_group(self.lang.get("emp_card_access", "Bank/Card Access"), card_accs, ex_card)
        access_layout.addWidget(card_group)
        
        access_layout.addStretch()
        access_scroll.setWidget(access_content)
        perms_layout.addWidget(access_scroll, 1)
        layout.addLayout(perms_layout)
        
        buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)

    def get_data(self):
        selected_permissions = []
        root = self.tree.invisibleRootItem()
        for i in range(root.childCount()):
            category_item = root.child(i)
            for j in range(category_item.childCount()):
                child = category_item.child(j)
                if child.checkState(0) == Qt.CheckState.Checked:
                    perm_id = child.data(0, Qt.ItemDataRole.UserRole)
                    if perm_id: selected_permissions.append(perm_id)
        selected_permissions.append("dashboard") 
        
        allowed_depots = [did for did, chk in self.depot_checks.items() if chk.isChecked()]
        allowed_cash = [cid for cid, chk in self.cash_checks.items() if chk.isChecked()]
        allowed_card = [cid for cid, chk in self.card_checks.items() if chk.isChecked()]
        
        return {
            "name": self.name_edit.text().strip(),
            "password": self.pass_edit.text(),
            "permissions": selected_permissions,
            "allowed_depots": allowed_depots,
            "allowed_cash_accounts": allowed_cash,
            "allowed_card_accounts": allowed_card
        }
class AdminManager:
    """
    Yonetici Islemleri (Ayarlar, Donanim, Kullanici)
    [AI_DOC]
    Barkod ve Donanim (Thermal Printer) ayarlarinin veritabanina 
    kaydedilmesinden de bu sinif sorumludur.
    [/AI_DOC]
    """
    def __init__(self, main_app):
        self.main_app = main_app
        self.settings_manager = main_app.settings_manager
        self.db = self.settings_manager.db
        self.profile_id = self.settings_manager.profile_id
        self.auth_repo = AuthRepository(self.db, self.profile_id)

    def has_admin_password(self, profile_id: Optional[str] = None) -> bool:
        return self.auth_repo.has_admin_password(profile_id)

    def _show_staff_creation_blocked_dialog(self) -> None:
        lang = getattr(self.main_app, "lang", load_language("TR"))
        msg_box = QMessageBox(self.main_app)
        msg_box.setIcon(QMessageBox.Icon.Warning)
        msg_box.setWindowTitle(lang.get("blocked_title", "Blocked"))
        msg_box.setText(
            lang.get(
                "staff_creation_blocked_no_admin_password",
                "This profile has no admin password set.\n"
                "You must set a profile password before adding staff.",
            )
        )
        open_btn = msg_box.addButton(
            lang.get("open_profile_security_settings", "Open Profile Security Settings"),
            QMessageBox.ButtonRole.ActionRole,
        )
        msg_box.addButton(lang.get("ok", "OK"), QMessageBox.ButtonRole.RejectRole)
        msg_box.exec()
        
        if msg_box.clickedButton() == open_btn and hasattr(self.main_app, "_open_profile_security_settings"):
            self.main_app._open_profile_security_settings()

    def _save_and_apply_settings(self) -> None:
        """
        [AI_DOC]
        Ayarlar -> Genel sekmesindeki verileri veritabanina kaydeder.
        Ayrica Barkod sistemi ve Donanim (Printer) tercihlerini de bu fonskiyon kaydeder.
        [/AI_DOC]
        """
        # Sirket / Dukkan Bilgileri
        shop_name = self.main_app.shop_name_edit.text().strip()
        shop_phone = self.main_app.shop_phone_edit.text().strip()
        self.settings_manager.set_setting("shop_name", shop_name)
        self.settings_manager.set_setting("shop_phone", shop_phone)

        company_name = self.main_app.company_name_edit.text().strip()
        company_address = self.main_app.company_address_edit.toPlainText().strip()
        tax_office = self.main_app.tax_office_edit.text().strip()
        tax_number = self.main_app.tax_number_edit.text().strip()
        company_phone = self.main_app.company_phone_edit.text().strip()
        company_email = self.main_app.company_email_edit.text().strip()
        document_prefix = (self.main_app.document_prefix_edit.text().strip() or "DOC")

        self.settings_manager.set_setting("company_name", company_name)
        self.settings_manager.set_setting("company_address", company_address)
        self.settings_manager.set_setting("tax_office", tax_office)
        self.settings_manager.set_setting("tax_number", tax_number)
        self.settings_manager.set_setting("company_phone", company_phone)
        self.settings_manager.set_setting("company_email", company_email)
        self.settings_manager.set_setting("document_prefix", document_prefix)

        # Gorunum
        new_theme = self.main_app.theme_combo.currentText()
        new_lang_code = self.main_app.language_combo.currentText()
        new_display_currency = self.main_app.display_currency_combo.currentText()
        
        lang_changed = self.main_app.current_lang_code != new_lang_code

        from core.config import set_global_setting
        self.settings_manager.set_setting("theme_name", new_theme)
        self.settings_manager.set_setting("language", new_lang_code)
        set_global_setting("language", new_lang_code)
        self.settings_manager.set_setting("display_currency", new_display_currency)

        new_tab_position = self.main_app.tab_position_combo.currentData() or self.main_app.tab_position_combo.currentText()
        self.settings_manager.set_setting("tab_position", new_tab_position)
        self.main_app._apply_tab_position()

        # ---------------------------------------------------------
        # DONANIM VE BARKOD AYARLARINI KAYDET (YENI EKLENDI)
        # ---------------------------------------------------------
        use_barcode = getattr(self.main_app, "use_barcode_checkbox", None)
        if use_barcode:
            self.settings_manager.set_setting("use_barcode_system", use_barcode.isChecked())

        printer_type = getattr(self.main_app, "printer_type_combo", None)
        if printer_type:
            self.settings_manager.set_setting("printer_type", printer_type.currentText())

        printer_name = getattr(self.main_app, "printer_name_entry", None)
        if printer_name:
            self.settings_manager.set_setting("printer_name", printer_name.text().strip())

        # Sifre Degisimi
        pass_text = self.main_app.password_edit.text().strip()
        if pass_text != "******":
            if not pass_text:
                self.auth_repo.clear_profile_password(profile_id=self.profile_id)
                self.settings_manager.settings["password_enabled"] = False
                self.settings_manager.settings["password_hash"] = ""
            else:
                self.auth_repo.set_profile_password(pass_text, profile_id=self.profile_id)
                _enabled, new_hash = self.auth_repo.get_profile_password_state(self.profile_id)
                self.settings_manager.settings["password_enabled"] = True
                self.settings_manager.settings["password_hash"] = new_hash

        msg = self.main_app.lang["settings_saved"]
        if lang_changed:
            msg += f"\n\n{self.main_app.lang['lang_restart_needed']}"
            
        QMessageBox.information(self.main_app, self.main_app.lang["success"], msg)

        if not lang_changed:
            self.main_app.display_currency = new_display_currency
            self.main_app.apply_theme()
            self.main_app.update_all_displays()

        if hasattr(self.main_app, "_apply_staff_creation_guard"):
            self.main_app._apply_staff_creation_guard(show_dialog=False)

    def _add_employee(self):
        if not self.has_admin_password(self.profile_id):
            self._show_staff_creation_blocked_dialog()
            return
            
        dialog = EmployeeEditorDialog(
            self.main_app.lang,
            self.main_app.depot_manager,
            self.main_app.cash_account_manager,
            self.main_app.card_account_manager,
            parent=self.main_app
        )
        if dialog.exec():
            data = dialog.get_data()
            if not data["name"] or not data["password"]:
                QMessageBox.warning(self.main_app, "Hata", "Isim ve Sifre zorunludur.")
                return
                
            username = data["name"].replace(" ", "").lower()
            if self.auth_repo.get_user_by_username(username):
                username += "_" + str(uuid.uuid4())[:4]
                
            secure_hash = self.auth_repo.hash_password(data["password"])
            emp_record = {
                "username": username,
                "name": data["name"],
                "password_hash": secure_hash,
                "permissions": data["permissions"],
                "allowed_depots": data["allowed_depots"],
                "allowed_cash_accounts": data["allowed_cash_accounts"],
                "allowed_card_accounts": data["allowed_card_accounts"],
                "role": "employee" 
            }
            try:
                self.auth_repo.create_staff(emp_record)
                if hasattr(self.main_app, "refresh_staff_table"):
                    self.main_app.refresh_staff_table()
                elif hasattr(self.main_app, "_refresh_employee_list"):
                    self.main_app._refresh_employee_list()
                QMessageBox.information(self.main_app, "Basarili", "Calisan eklendi.")
            except Exception as e:
                QMessageBox.critical(self.main_app, "Hata", f"Eklenemedi: {e}")

    def _edit_employee(self):
        curr = self.main_app.emp_list.currentItem()
        if not curr: return
        user_id = curr.data(Qt.ItemDataRole.UserRole)
        user_info = self.auth_repo.get_user_by_id(user_id)
        if not user_info: return
        
        user_info['permissions'] = self.auth_repo.get_user_permissions(user_id)
        user_info['allowed_depots'] = self.auth_repo.get_access_scopes(user_id, 'depot')
        user_info['allowed_cash_accounts'] = self.auth_repo.get_access_scopes(user_id, 'cash_account')
        user_info['allowed_card_accounts'] = self.auth_repo.get_access_scopes(user_id, 'card_account')
        
        dialog = EmployeeEditorDialog(
            self.main_app.lang,
            self.main_app.depot_manager,
            self.main_app.cash_account_manager,
            self.main_app.card_account_manager,
            employee_data=user_info, parent=self.main_app
        )
        if dialog.exec():
            data = dialog.get_data()
            update_data = {
                "name": data["name"],
                "permissions": data["permissions"],
                "allowed_depots": data["allowed_depots"],
                "allowed_cash_accounts": data["allowed_cash_accounts"],
                "allowed_card_accounts": data["allowed_card_accounts"]
            }
            if data["password"]:
                update_data["password_hash"] = self.auth_repo.hash_password(data["password"])
                
            try:
                self.auth_repo.update_user(user_id, update_data)
                if hasattr(self.main_app, "refresh_staff_table"):
                    self.main_app.refresh_staff_table()
                elif hasattr(self.main_app, "_refresh_employee_list"):
                    self.main_app._refresh_employee_list()
                QMessageBox.information(self.main_app, "Basarili", "Calisan guncellendi.")
            except Exception as e:
                QMessageBox.critical(self.main_app, "Hata", f"Guncellenemedi: {e}")

    def _delete_employee(self):
        curr = self.main_app.emp_list.currentItem()
        if not curr: return
        if self.main_app._show_confirm_dialog("Sil", "Calisani silmek istediginize emin misiniz?"):
            user_id = curr.data(Qt.ItemDataRole.UserRole)
            user_info = self.auth_repo.get_user_by_id(user_id)
            if user_info and user_info['role'] == 'admin':
                QMessageBox.warning(self.main_app, "Hata", "Admin hesabi silinemez.")
                return
            try:
                self.auth_repo.delete_user(user_id)
                if hasattr(self.main_app, "refresh_staff_table"):
                    self.main_app.refresh_staff_table()
                elif hasattr(self.main_app, "_refresh_employee_list"):
                    self.main_app._refresh_employee_list()
                QMessageBox.information(self.main_app, "Basarili", "Calisan silindi.")
            except Exception as e:
                QMessageBox.critical(self.main_app, "Hata", f"Silinemedi: {e}")

    def _ensure_backup_plan(self) -> bool:
        from core.tenant_manager import TenantManager
        tenant = TenantManager()
        if not tenant.can_use_backup_features():
            QMessageBox.warning(self.main_app, "Uyari", "Yedekleme bu lisans planinda aktif degil.")
            return False
        return True

    def _export_current_profile(self):
        if not self._ensure_backup_plan(): return
        from core.backup_service import backup_storage_dir, create_backup_bundle, default_backup_filename, manual_backup_daily_count, MANUAL_BACKUP_DAILY_LIMIT
        
        used = manual_backup_daily_count()
        if used >= MANUAL_BACKUP_DAILY_LIMIT:
            QMessageBox.warning(self.main_app, "Uyari", f"Gunluk yedek limitine ulasildi.\nKullanilan: {used} / {MANUAL_BACKUP_DAILY_LIMIT}")
            return
            
        default_name = default_backup_filename(self.profile_id, 1, "manual")
        default_path = str(backup_storage_dir() / default_name)
        file_path, _ = QFileDialog.getSaveFileName(self.main_app, "Yedek Kaydet", default_path, "Golder Yedek (*.golder)")
        
        if not file_path: return
        result = create_backup_bundle(self.db, [self.profile_id], "manual", target_path=file_path)
        if not result.get("ok"):
            QMessageBox.critical(self.main_app, "Hata", str(result.get("message") or "Yedek olusturulamadi."))
            return
        QMessageBox.information(self.main_app, "Basarili", f"Yedek kaydedildi.\n\nDosya: {result.get('file_path')}")

    def _backup_manual_download(self) -> None:
        if not self._ensure_backup_plan(): return
        from ui.dialogs import BackupProfilePickerDialog
        from core.backup_service import backup_storage_dir, create_backup_bundle, default_backup_filename, manual_backup_daily_count, MANUAL_BACKUP_DAILY_LIMIT
        
        used = manual_backup_daily_count()
        if used >= MANUAL_BACKUP_DAILY_LIMIT:
            QMessageBox.warning(self.main_app, "Uyari", f"Gunluk limit dolu ({MANUAL_BACKUP_DAILY_LIMIT}/gun).")
            return
            
        picker = BackupProfilePickerDialog(self.db, self.profile_id, parent=self.main_app)
        if picker.exec() != QDialog.DialogCode.Accepted: return
        
        profile_ids = picker.selected_profile_ids()
        if not profile_ids: return
        
        default_name = default_backup_filename(profile_ids[0], len(profile_ids), "manual")
        default_path = str(backup_storage_dir() / default_name)
        file_path, _ = QFileDialog.getSaveFileName(self.main_app, "Yedek Kaydet", default_path, "Golder Yedek (*.golder)")
        
        if not file_path: return
        result = create_backup_bundle(self.db, profile_ids, "manual", target_path=file_path)
        if not result.get("ok"):
            QMessageBox.critical(self.main_app, "Hata", str(result.get("message")))
            return
        QMessageBox.information(self.main_app, "Basarili", f"Yedek alindi:\n{result.get('file_path')}")

    def _restore_backup_from_file(self) -> None:
        if not self._ensure_backup_plan(): return
        from core.backup_service import restore_backup_bundle
        
        file_path, _ = QFileDialog.getOpenFileName(self.main_app, "Yedek Dosyasi Sec", str(Path.home()), "Golder Yedek (*.golder)")
        if not file_path: return
        
        try:
            result = restore_backup_bundle(self.db, file_path)
        except Exception as exc:
            QMessageBox.critical(self.main_app, "Hata", f"Yedek geri yuklenemedi: {exc}")
            return
            
        if not result.get("ok"):
            QMessageBox.warning(self.main_app, "Uyari", str(result.get("message")))
            return
            
        QMessageBox.information(self.main_app, "Basarili", "Yedek geri yuklendi. Sistemi yeniden baslatmaniz gerekebilir.")

    def _select_and_update_logo(self):
        import shutil
        import os
        file_path, _ = QFileDialog.getOpenFileName(self.main_app, "Logo Sec", "", "Resim Dosyalari (*.png *.jpg *.jpeg *.bmp)")
        if not file_path: return
        
        try:
            target_path = RESOURCES_DIR / "golder.png"
            backup_path = RESOURCES_DIR / "golder_backup.png"
            RESOURCES_DIR.mkdir(parents=True, exist_ok=True)
            
            if target_path.exists():
                if backup_path.exists():
                    try: os.remove(backup_path)
                    except: pass
                shutil.copy2(target_path, backup_path)
                
            if target_path.exists():
                os.remove(target_path)
            shutil.copy2(file_path, target_path)
            
            self.main_app._update_app_logo()
            QMessageBox.information(self.main_app, "Basarili", "Logo guncellendi.")
        except Exception as e:
            QMessageBox.critical(self.main_app, "Hata", f"Logo degistirilemedi: {e}")

    def _restore_backup_logo(self):
        import shutil
        import os
        target_path = RESOURCES_DIR / "golder.png"
        backup_path = RESOURCES_DIR / "golder_backup.png"
        
        if not backup_path.exists():
            QMessageBox.warning(self.main_app, "Uyari", "Yedek logo bulunamadi.")
            return
            
        try:
            if target_path.exists():
                os.remove(target_path)
            shutil.copy2(backup_path, target_path)
            self.main_app._update_app_logo()
            QMessageBox.information(self.main_app, "Basarili", "Onceki logo geri yuklendi.")
        except Exception as e:
            QMessageBox.critical(self.main_app, "Hata", f"Geri yukleme basarisiz: {e}")




