<?php
/**
 * Klasa Setting - zarządzanie ustawieniami urządzeń
 */

class Setting
{
    private Database $db;

    public function __construct()
    {
        $this->db = Database::getInstance();
    }

    // ==================== DEFINITIONS ====================

    /**
     * Pobierz wszystkie definicje
     */
    public function getDefinitions(?string $groupName = null, bool $activeOnly = true): array
    {
        $sql = "SELECT * FROM settings_definitions";
        $where = [];
        $params = [];

        if ($activeOnly) {
            $where[] = "is_active = 1";
        }

        if ($groupName) {
            $where[] = "group_name = :group_name";
            $params['group_name'] = $groupName;
        }

        if (!empty($where)) {
            $sql .= " WHERE " . implode(' AND ', $where);
        }

        $sql .= " ORDER BY group_name, sort_order, label";

        $definitions = $this->db->fetchAll($sql, $params);

        // Dekoduj options_json
        foreach ($definitions as &$def) {
            $def['options'] = $def['options_json'] ? json_decode($def['options_json'], true) : null;
        }

        return $definitions;
    }

    /**
     * Pobierz definicję po ID
     */
    public function getDefinitionById(int $id): ?array
    {
        $def = $this->db->fetchOne(
            "SELECT * FROM settings_definitions WHERE id = :id",
            ['id' => $id]
        );

        if ($def) {
            $def['options'] = $def['options_json'] ? json_decode($def['options_json'], true) : null;
        }

        return $def;
    }

    /**
     * Pobierz definicję po kluczu
     */
    public function getDefinitionByKey(string $key): ?array
    {
        $def = $this->db->fetchOne(
            "SELECT * FROM settings_definitions WHERE key = :key",
            ['key' => $key]
        );

        if ($def) {
            $def['options'] = $def['options_json'] ? json_decode($def['options_json'], true) : null;
        }

        return $def;
    }

    /**
     * Pobierz grupy ustawień
     */
    public function getGroups(): array
    {
        return $this->db->fetchAll(
            "SELECT DISTINCT group_name, COUNT(*) as count 
             FROM settings_definitions 
             WHERE is_active = 1 
             GROUP BY group_name 
             ORDER BY group_name"
        );
    }

    /**
     * Utwórz definicję
     */
    public function createDefinition(array $data): int
    {
        $optionsJson = null;
        if (!empty($data['options']) && is_array($data['options'])) {
            $optionsJson = json_encode($data['options'], JSON_UNESCAPED_UNICODE);
        }

        return $this->db->insert(
            "INSERT INTO settings_definitions (key, type, default_value, label, description, group_name, min_scope, options_json, sort_order, is_active)
             VALUES (:key, :type, :default_value, :label, :description, :group_name, :min_scope, :options_json, :sort_order, :is_active)",
            [
                'key' => $data['key'],
                'type' => $data['type'],
                'default_value' => $data['default_value'] ?? null,
                'label' => $data['label'],
                'description' => $data['description'] ?? null,
                'group_name' => $data['group_name'] ?? 'general',
                'min_scope' => $data['min_scope'] ?? 'device',
                'options_json' => $optionsJson,
                'sort_order' => $data['sort_order'] ?? 0,
                'is_active' => $data['is_active'] ?? 1
            ]
        );
    }

    /**
     * Aktualizuj definicję
     */
    public function updateDefinition(int $id, array $data): bool
    {
        $optionsJson = null;
        if (isset($data['options']) && is_array($data['options'])) {
            $optionsJson = json_encode($data['options'], JSON_UNESCAPED_UNICODE);
        }

        return $this->db->execute(
            "UPDATE settings_definitions SET
                key = :key,
                type = :type,
                default_value = :default_value,
                label = :label,
                description = :description,
                group_name = :group_name,
                min_scope = :min_scope,
                options_json = :options_json,
                sort_order = :sort_order,
                is_active = :is_active,
                updated_at = CURRENT_TIMESTAMP
             WHERE id = :id",
            [
                'id' => $id,
                'key' => $data['key'],
                'type' => $data['type'],
                'default_value' => $data['default_value'] ?? null,
                'label' => $data['label'],
                'description' => $data['description'] ?? null,
                'group_name' => $data['group_name'] ?? 'general',
                'min_scope' => $data['min_scope'] ?? 'device',
                'options_json' => $optionsJson,
                'sort_order' => $data['sort_order'] ?? 0,
                'is_active' => $data['is_active'] ?? 1
            ]
        );
    }

    /**
     * Usuń definicję
     */
    public function deleteDefinition(int $id): bool
    {
        return $this->db->execute(
            "DELETE FROM settings_definitions WHERE id = :id",
            ['id' => $id]
        );
    }

    // ==================== PRESETS ====================

    /**
     * Pobierz wszystkie presety
     */
    public function getPresets(bool $activeOnly = true): array
    {
        $sql = "SELECT p.*, u.username as created_by_name,
                       (SELECT COUNT(*) FROM settings_preset_items WHERE preset_id = p.id) as items_count
                FROM settings_presets p
                LEFT JOIN users u ON p.created_by = u.id";

        if ($activeOnly) {
            $sql .= " WHERE p.is_active = 1";
        }

        $sql .= " ORDER BY p.name";

        return $this->db->fetchAll($sql);
    }

    /**
     * Pobierz preset po ID z elementami
     */
    public function getPresetById(int $id): ?array
    {
        $preset = $this->db->fetchOne(
            "SELECT p.*, u.username as created_by_name
             FROM settings_presets p
             LEFT JOIN users u ON p.created_by = u.id
             WHERE p.id = :id",
            ['id' => $id]
        );

        if ($preset) {
            $preset['items'] = $this->db->fetchAll(
                "SELECT pi.*, sd.key, sd.type, sd.label, sd.group_name
                 FROM settings_preset_items pi
                 JOIN settings_definitions sd ON pi.definition_id = sd.id
                 WHERE pi.preset_id = :preset_id
                 ORDER BY sd.group_name, sd.sort_order",
                ['preset_id' => $id]
            );
        }

        return $preset;
    }

    /**
     * Utwórz preset
     */
    public function createPreset(array $data, int $createdBy): int
    {
        return $this->db->insert(
            "INSERT INTO settings_presets (name, description, created_by, is_active)
             VALUES (:name, :description, :created_by, :is_active)",
            [
                'name' => $data['name'],
                'description' => $data['description'] ?? null,
                'created_by' => $createdBy,
                'is_active' => $data['is_active'] ?? 1
            ]
        );
    }

    /**
     * Aktualizuj preset
     */
    public function updatePreset(int $id, array $data): bool
    {
        return $this->db->execute(
            "UPDATE settings_presets SET
                name = :name,
                description = :description,
                is_active = :is_active,
                updated_at = CURRENT_TIMESTAMP
             WHERE id = :id",
            [
                'id' => $id,
                'name' => $data['name'],
                'description' => $data['description'] ?? null,
                'is_active' => $data['is_active'] ?? 1
            ]
        );
    }

    /**
     * Usuń preset
     */
    public function deletePreset(int $id): bool
    {
        return $this->db->execute(
            "DELETE FROM settings_presets WHERE id = :id",
            ['id' => $id]
        );
    }

    /**
     * Ustaw elementy presetu
     */
    public function setPresetItems(int $presetId, array $items): bool
    {
        // Usuń stare elementy
        $this->db->execute(
            "DELETE FROM settings_preset_items WHERE preset_id = :preset_id",
            ['preset_id' => $presetId]
        );

        // Dodaj nowe
        foreach ($items as $definitionId => $value) {
            $this->db->execute(
                "INSERT INTO settings_preset_items (preset_id, definition_id, value)
                 VALUES (:preset_id, :definition_id, :value)",
                [
                    'preset_id' => $presetId,
                    'definition_id' => $definitionId,
                    'value' => $value
                ]
            );
        }

        return true;
    }

    // ==================== VALUES ====================

    /**
     * Pobierz wartości dla scope
     */
    public function getValues(string $scope, ?int $scopeId = null): array
    {
        $sql = "SELECT sv.*, sd.key, sd.type, sd.label, sd.group_name, sd.default_value, sd.options_json
                FROM settings_values sv
                JOIN settings_definitions sd ON sv.definition_id = sd.id
                WHERE sv.scope = :scope";
        $params = ['scope' => $scope];

        if ($scopeId !== null) {
            $sql .= " AND sv.scope_id = :scope_id";
            $params['scope_id'] = $scopeId;
        } else {
            $sql .= " AND sv.scope_id IS NULL";
        }

        $sql .= " ORDER BY sd.group_name, sd.sort_order";

        $values = $this->db->fetchAll($sql, $params);

        foreach ($values as &$val) {
            $val['options'] = $val['options_json'] ? json_decode($val['options_json'], true) : null;
        }

        return $values;
    }

    /**
     * Pobierz definicje dostępne dla scope z aktualnymi wartościami
     */
    public function getDefinitionsWithValues(string $scope, ?int $scopeId = null): array
    {
        // min_scope określa NAJWYŻSZY poziom w hierarchii który może edytować ustawienie
        // Hierarchia (od najwyższego): global(1) > reseller(2) > user(3) > device(4)
        // 
        // min_scope='global' → tylko global może edytować (poziom 1)
        // min_scope='reseller' → global i reseller mogą edytować (poziomy 1-2)
        // min_scope='user' → global, reseller i user mogą edytować (poziomy 1-3)
        // min_scope='device' → wszyscy mogą edytować (poziomy 1-4)
        //
        // Czyli: currentLevel musi być <= min_scope_level
        
        if ($scope === 'global') {
            // Global widzi wszystko
            $definitions = $this->db->fetchAll(
                "SELECT * FROM settings_definitions 
                 WHERE is_active = 1 
                 ORDER BY group_name, sort_order, label"
            );
        } else {
            // Mapowanie scope na poziom (niższy numer = wyższe uprawnienia)
            $scopeLevels = [
                'global' => 1,
                'reseller' => 2,
                'user' => 3,
                'device' => 4
            ];

            $currentLevel = $scopeLevels[$scope] ?? 4;

            // Pobierz definicje gdzie obecny poziom może edytować
            // min_scope='user' (poziom 3) oznacza że user(3) i device(4) mogą edytować
            // Czyli: currentLevel (np. 3 dla user) musi być >= min_scope_level (np. 3 dla user)
            // Ale min_scope='global' (poziom 1) oznacza tylko global może edytować
            // Czyli dla user(3): 3 >= 1 jest true - ZŁE!
            // 
            // Poprawna logika: currentLevel <= min_scope_level
            // user(3) <= global(1) = false - nie widzi
            // user(3) <= user(3) = true - widzi
            // user(3) <= device(4) = true - widzi
            $definitions = $this->db->fetchAll(
                "SELECT * FROM settings_definitions 
                 WHERE is_active = 1 
                 AND :level <= (CASE min_scope 
                    WHEN 'global' THEN 1 
                    WHEN 'reseller' THEN 2 
                    WHEN 'user' THEN 3 
                    WHEN 'device' THEN 4 
                 END)
                 ORDER BY group_name, sort_order, label",
                ['level' => $currentLevel]
            );
        }

        // Pobierz aktualne wartości dla tego scope
        $currentValues = [];
        $rows = $this->getValues($scope, $scopeId);
        foreach ($rows as $row) {
            $currentValues[$row['definition_id']] = $row['value'];
        }

        // Dodaj wartości do definicji
        foreach ($definitions as &$def) {
            $def['options'] = $def['options_json'] ? json_decode($def['options_json'], true) : null;
            $def['current_value'] = $currentValues[$def['id']] ?? null;
            $def['has_value'] = isset($currentValues[$def['id']]);
        }

        return $definitions;
    }

    /**
     * Ustaw wartość
     */
    public function setValue(string $scope, ?int $scopeId, int $definitionId, string $value, int $updatedBy): bool
    {
        // Sprawdź czy wartość już istnieje
        $existing = $this->db->fetchOne(
            "SELECT id FROM settings_values 
             WHERE scope = :scope AND (scope_id = :scope_id OR (scope_id IS NULL AND :scope_id IS NULL)) AND definition_id = :definition_id",
            ['scope' => $scope, 'scope_id' => $scopeId, 'definition_id' => $definitionId]
        );

        if ($existing) {
            $result = $this->db->execute(
                "UPDATE settings_values SET
                    value = :value,
                    updated_at = CURRENT_TIMESTAMP,
                    updated_by = :updated_by
                 WHERE id = :id",
                ['id' => $existing['id'], 'value' => $value, 'updated_by' => $updatedBy]
            );
        } else {
            $result = $this->db->execute(
                "INSERT INTO settings_values (scope, scope_id, definition_id, value, updated_by)
                 VALUES (:scope, :scope_id, :definition_id, :value, :updated_by)",
                [
                    'scope' => $scope,
                    'scope_id' => $scopeId,
                    'definition_id' => $definitionId,
                    'value' => $value,
                    'updated_by' => $updatedBy
                ]
            );
        }

        if ($result) {
            $this->updateTimestamp($scope, $scopeId);
        }

        return $result;
    }

    /**
     * Ustaw wiele wartości naraz
     */
    public function setValues(string $scope, ?int $scopeId, array $values, int $updatedBy): bool
    {
        $this->db->beginTransaction();

        try {
            foreach ($values as $definitionId => $value) {
                $this->setValue($scope, $scopeId, (int)$definitionId, (string)$value, $updatedBy);
            }

            $this->db->commit();
            return true;
        } catch (Exception $e) {
            $this->db->rollback();
            return false;
        }
    }

    /**
     * Usuń wartość
     */
    public function deleteValue(string $scope, ?int $scopeId, int $definitionId): bool
    {
        $result = $this->db->execute(
            "DELETE FROM settings_values 
             WHERE scope = :scope AND (scope_id = :scope_id OR (scope_id IS NULL AND :scope_id IS NULL)) AND definition_id = :definition_id",
            ['scope' => $scope, 'scope_id' => $scopeId, 'definition_id' => $definitionId]
        );

        if ($result && $this->db->rowCount() > 0) {
            $this->updateTimestamp($scope, $scopeId);
        }

        return $result;
    }

    /**
     * Usuń wszystkie wartości dla scope
     */
    public function deleteAllValues(string $scope, ?int $scopeId): bool
    {
        $result = $this->db->execute(
            "DELETE FROM settings_values 
             WHERE scope = :scope AND (scope_id = :scope_id OR (scope_id IS NULL AND :scope_id IS NULL))",
            ['scope' => $scope, 'scope_id' => $scopeId]
        );

        if ($result) {
            $this->updateTimestamp($scope, $scopeId);
        }

        return $result;
    }

    /**
     * Zastosuj preset do scope
     */
    public function applyPreset(int $presetId, string $scope, ?int $scopeId, int $updatedBy): bool
    {
        $preset = $this->getPresetById($presetId);
        if (!$preset) {
            return false;
        }

        $values = [];
        foreach ($preset['items'] as $item) {
            $values[$item['definition_id']] = $item['value'];
        }

        return $this->setValues($scope, $scopeId, $values, $updatedBy);
    }

    // ==================== TIMESTAMPS ====================

    /**
     * Aktualizuj timestamp dla scope
     */
    public function updateTimestamp(string $scope, ?int $scopeId): bool
    {
        $timestamp = time();

        $existing = $this->db->fetchOne(
            "SELECT id FROM settings_timestamps 
             WHERE scope = :scope AND (scope_id = :scope_id OR (scope_id IS NULL AND :scope_id IS NULL))",
            ['scope' => $scope, 'scope_id' => $scopeId]
        );

        if ($existing) {
            return $this->db->execute(
                "UPDATE settings_timestamps SET updated_at = :updated_at WHERE id = :id",
                ['id' => $existing['id'], 'updated_at' => $timestamp]
            );
        } else {
            return $this->db->execute(
                "INSERT INTO settings_timestamps (scope, scope_id, updated_at) VALUES (:scope, :scope_id, :updated_at)",
                ['scope' => $scope, 'scope_id' => $scopeId, 'updated_at' => $timestamp]
            );
        }
    }

    /**
     * Pobierz timestamp dla scope
     */
    public function getTimestamp(string $scope, ?int $scopeId): ?int
    {
        $row = $this->db->fetchOne(
            "SELECT updated_at FROM settings_timestamps 
             WHERE scope = :scope AND (scope_id = :scope_id OR (scope_id IS NULL AND :scope_id IS NULL))",
            ['scope' => $scope, 'scope_id' => $scopeId]
        );

        return $row ? (int)$row['updated_at'] : null;
    }

    // ==================== API ====================

    /**
     * Pobierz ustawienia dla urządzenia (do API)
     * Hierarchia: device > user > reseller > global
     */
    public function getForDevice(int $deviceId): array
    {
        // Pobierz dane urządzenia i użytkownika
        $device = $this->db->fetchOne(
            "SELECT d.*, u.parent_id as reseller_id 
             FROM devices d 
             JOIN users u ON d.user_id = u.id 
             WHERE d.id = :id",
            ['id' => $deviceId]
        );

        if (!$device) {
            return ['timestamp' => 0, 'values' => []];
        }

        $userId = $device['user_id'];
        $resellerId = $device['reseller_id'];

        // Pobierz wszystkie aktywne definicje
        $definitions = $this->db->fetchAll(
            "SELECT id, key, type, default_value FROM settings_definitions WHERE is_active = 1"
        );

        // Mapuj definicje po ID
        $defMap = [];
        foreach ($definitions as $def) {
            $defMap[$def['id']] = $def;
        }

        // Pobierz wartości z każdego poziomu
        $globalValues = $this->getValuesMap('global', null);
        $resellerValues = $resellerId ? $this->getValuesMap('reseller', $resellerId) : [];
        $userValues = $this->getValuesMap('user', $userId);
        $deviceValues = $this->getValuesMap('device', $deviceId);

        // Pobierz timestampy
        $globalTs = $this->getTimestamp('global', null) ?? 0;
        $resellerTs = $resellerId ? ($this->getTimestamp('reseller', $resellerId) ?? 0) : 0;
        $userTs = $this->getTimestamp('user', $userId) ?? 0;
        $deviceTs = $this->getTimestamp('device', $deviceId) ?? 0;

        // Scal wartości (priorytet: device > user > reseller > global > default)
        $merged = [];
        $effectiveTimestamp = $globalTs;

        foreach ($definitions as $def) {
            $key = $def['key'];
            $type = $def['type'];
            $defId = $def['id'];

            // Znajdź wartość z najniższego dostępnego poziomu
            $value = $def['default_value'];
            $sourceTs = 0;

            if (isset($globalValues[$defId])) {
                $value = $globalValues[$defId];
                $sourceTs = $globalTs;
            }

            if (isset($resellerValues[$defId])) {
                $value = $resellerValues[$defId];
                $sourceTs = $resellerTs;
            }

            if (isset($userValues[$defId])) {
                $value = $userValues[$defId];
                $sourceTs = $userTs;
            }

            if (isset($deviceValues[$defId])) {
                $value = $deviceValues[$defId];
                $sourceTs = $deviceTs;
            }

            // Konwertuj wartość do odpowiedniego typu
            $typedValue = $this->castValue($value, $type);

            $merged[$key] = [
                'type' => $type,
                'value' => $typedValue
            ];

            // Aktualizuj efektywny timestamp (najnowszy ze źródeł)
            if ($sourceTs > $effectiveTimestamp) {
                $effectiveTimestamp = $sourceTs;
            }
        }

        return [
            'timestamp' => $effectiveTimestamp,
            'values' => $merged
        ];
    }

    /**
     * Pomocnicza - pobierz wartości jako mapę definition_id => value
     */
    private function getValuesMap(string $scope, ?int $scopeId): array
    {
        $sql = "SELECT definition_id, value FROM settings_values WHERE scope = :scope";
        $params = ['scope' => $scope];

        if ($scopeId !== null) {
            $sql .= " AND scope_id = :scope_id";
            $params['scope_id'] = $scopeId;
        } else {
            $sql .= " AND scope_id IS NULL";
        }

        $rows = $this->db->fetchAll($sql, $params);

        $map = [];
        foreach ($rows as $row) {
            $map[$row['definition_id']] = $row['value'];
        }

        return $map;
    }

    /**
     * Konwertuj wartość do odpowiedniego typu
     */
    private function castValue($value, string $type)
    {
        if ($value === null) {
            return null;
        }

        switch ($type) {
            case 'boolean':
                return filter_var($value, FILTER_VALIDATE_BOOLEAN);
            case 'int':
                return (int)$value;
            case 'long':
                return (int)$value;
            case 'string':
            default:
                return (string)$value;
        }
    }

    /**
     * Zlicz definicje
     */
    public function countDefinitions(): int
    {
        $result = $this->db->fetchOne("SELECT COUNT(*) as count FROM settings_definitions WHERE is_active = 1");
        return $result['count'] ?? 0;
    }

    /**
     * Zlicz presety
     */
    public function countPresets(): int
    {
        $result = $this->db->fetchOne("SELECT COUNT(*) as count FROM settings_presets WHERE is_active = 1");
        return $result['count'] ?? 0;
    }

    /**
     * Zlicz wartości dla scope
     */
    public function countValues(string $scope, ?int $scopeId = null): int
    {
        $sql = "SELECT COUNT(*) as count FROM settings_values WHERE scope = :scope";
        $params = ['scope' => $scope];

        if ($scopeId !== null) {
            $sql .= " AND scope_id = :scope_id";
            $params['scope_id'] = $scopeId;
        } else {
            $sql .= " AND scope_id IS NULL";
        }

        $result = $this->db->fetchOne($sql, $params);
        return $result['count'] ?? 0;
    }
}
