<?php
declare(strict_types=1);

namespace App\Modules\Rules\Services;

use App\Core\App;
use App\Core\Logger;
use App\Modules\Rules\Models\BusinessRule;
use App\Modules\Rules\Models\RuleVersion;
use App\Modules\Rules\Models\RuleOverride;

final class RuleEngine
{
    private static array $cache = [];

    public static function get(string $ruleCode, ?int $branchId = null, ?string $date = null): mixed
    {
        $date = $date ?? date('Y-m-d');
        $cacheKey = "{$ruleCode}_{$branchId}_{$date}";

        if (isset(self::$cache[$cacheKey])) {
            return self::$cache[$cacheKey];
        }

        $db = App::getInstance()->db();

        // Try branch-specific first
        $rule = null;
        if ($branchId !== null) {
            $rule = $db->selectOne(
                "SELECT * FROM business_rules WHERE rule_code = ? AND branch_id = ? AND is_active = 1 AND effective_from <= ? AND (effective_to IS NULL OR effective_to >= ?)",
                [$ruleCode, $branchId, $date, $date]
            );
        }

        // Fallback to global
        if (!$rule) {
            $rule = $db->selectOne(
                "SELECT * FROM business_rules WHERE rule_code = ? AND branch_id IS NULL AND is_active = 1 AND effective_from <= ? AND (effective_to IS NULL OR effective_to >= ?)",
                [$ruleCode, $date, $date]
            );
        }

        if (!$rule) {
            Logger::warning("Rule not found: {$ruleCode}", ['branch_id' => $branchId, 'date' => $date]);
            self::$cache[$cacheKey] = null;
            return null;
        }

        $value = json_decode($rule['current_value_json'], true);
        self::$cache[$cacheKey] = $value;
        return $value;
    }

    public static function getValue(string $ruleCode, string $key = 'value', ?int $branchId = null): mixed
    {
        $data = self::get($ruleCode, $branchId);
        if ($data === null) {
            return null;
        }
        return $data[$key] ?? ($data['percentage'] ?? ($data['amount'] ?? ($data['value'] ?? null)));
    }

    public static function getAll(string $category, ?int $branchId = null): array
    {
        return BusinessRule::allByCategory($category, $branchId);
    }

    public static function update(string $ruleCode, string $newValueJson, string $reason, ?int $branchId = null, ?string $effectiveFrom = null): void
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();
        $effectiveFrom = $effectiveFrom ?? date('Y-m-d');

        $where = 'rule_code = ? AND is_active = 1 AND effective_from <= ? AND (effective_to IS NULL OR effective_to >= ?)';
        $params = [$ruleCode, $effectiveFrom, $effectiveFrom];
        if ($branchId !== null) {
            $where .= ' AND branch_id = ?';
            $params[] = $branchId;
        } else {
            $where .= ' AND branch_id IS NULL';
        }

        $rule = $db->selectOne("SELECT * FROM business_rules WHERE {$where} ORDER BY effective_from DESC LIMIT 1", $params);
        if (!$rule) {
            $fallback = $db->selectOne(
                "SELECT * FROM business_rules WHERE rule_code = ? AND is_active = 1" . ($branchId !== null ? " AND branch_id = ?" : " AND branch_id IS NULL") . " ORDER BY effective_from DESC LIMIT 1",
                $branchId !== null ? [$ruleCode, $branchId] : [$ruleCode]
            );
            if (!$fallback) {
                throw new \RuntimeException("Rule not found: {$ruleCode}");
            }
            $rule = $fallback;
        }

        $newVersion = (int) $rule['version'] + 1;

        $db->insert('rule_versions', [
            'rule_id'        => (int) $rule['id'],
            'version_number' => $newVersion,
            'old_value_json' => $rule['current_value_json'],
            'new_value_json' => $newValueJson,
            'changed_by'     => $employee ? (int) $employee->id : null,
            'changed_at'     => date('Y-m-d H:i:s'),
            'change_reason'  => $reason,
        ]);

        if ($rule['effective_from'] === $effectiveFrom) {
            $db->update('business_rules', [
                'current_value_json' => $newValueJson,
                'version'            => $newVersion,
                'updated_at'         => date('Y-m-d H:i:s'),
                'updated_by'         => $employee ? (int) $employee->id : null,
            ], '`id` = ?', [$rule['id']]);
        } else {
            $yesterday = date('Y-m-d', strtotime($effectiveFrom . ' -1 day'));
            $db->update('business_rules', [
                'effective_to' => $yesterday,
                'updated_at'   => date('Y-m-d H:i:s'),
                'updated_by'   => $employee ? (int) $employee->id : null,
            ], '`id` = ?', [$rule['id']]);

            $db->insert('business_rules', [
                'rule_code'          => $rule['rule_code'],
                'category'           => $rule['category'],
                'name_ar'            => $rule['name_ar'],
                'name_en'            => $rule['name_en'],
                'description_ar'     => $rule['description_ar'],
                'description_en'     => $rule['description_en'],
                'parameters_json'    => $rule['parameters_json'],
                'current_value_json' => $newValueJson,
                'data_type'          => $rule['data_type'],
                'branch_id'          => $rule['branch_id'],
                'effective_from'     => $effectiveFrom,
                'effective_to'       => $rule['effective_to'],
                'is_active'          => 1,
                'version'            => $newVersion,
                'created_at'         => date('Y-m-d H:i:s'),
                'updated_at'         => date('Y-m-d H:i:s'),
                'created_by'         => $employee ? (int) $employee->id : null,
                'updated_by'         => $employee ? (int) $employee->id : null,
            ]);
        }

        self::$cache = [];
        Logger::info("Rule updated: {$ruleCode}", ['version' => $newVersion, 'effective_from' => $effectiveFrom, 'reason' => $reason]);
    }

    public static function addHistoricalVersion(string $ruleCode, string $valueJson, string $effectiveFrom, string $effectiveTo, string $reason, ?int $branchId = null): void
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $existing = $db->selectOne(
            "SELECT * FROM business_rules WHERE rule_code = ? AND is_active = 1" . ($branchId !== null ? " AND branch_id = ?" : " AND branch_id IS NULL") . " LIMIT 1",
            $branchId !== null ? [$ruleCode, $branchId] : [$ruleCode]
        );
        if (!$existing) {
            throw new \RuntimeException("Rule not found: {$ruleCode}");
        }

        $overlap = $db->selectOne(
            "SELECT id FROM business_rules WHERE rule_code = ? AND is_active = 1 AND effective_from <= ? AND (effective_to IS NULL OR effective_to >= ?)" . ($branchId !== null ? " AND branch_id = ?" : " AND branch_id IS NULL"),
            $branchId !== null ? [$ruleCode, $effectiveTo, $effectiveFrom, $branchId] : [$ruleCode, $effectiveTo, $effectiveFrom]
        );
        if ($overlap) {
            throw new \RuntimeException("Date range overlaps with existing version for rule: {$ruleCode}");
        }

        $db->insert('business_rules', [
            'rule_code'          => $existing['rule_code'],
            'category'           => $existing['category'],
            'name_ar'            => $existing['name_ar'],
            'name_en'            => $existing['name_en'],
            'description_ar'     => $existing['description_ar'],
            'description_en'     => $existing['description_en'],
            'parameters_json'    => $existing['parameters_json'],
            'current_value_json' => $valueJson,
            'data_type'          => $existing['data_type'],
            'branch_id'          => $existing['branch_id'],
            'effective_from'     => $effectiveFrom,
            'effective_to'       => $effectiveTo,
            'is_active'          => 1,
            'version'            => 1,
            'created_at'         => date('Y-m-d H:i:s'),
            'updated_at'         => date('Y-m-d H:i:s'),
            'created_by'         => $employee ? (int) $employee->id : null,
            'updated_by'         => $employee ? (int) $employee->id : null,
        ]);

        self::$cache = [];
        Logger::info("Historical version added: {$ruleCode}", ['from' => $effectiveFrom, 'to' => $effectiveTo, 'reason' => $reason]);
    }

    public static function getVersionHistory(string $ruleCode, ?int $branchId = null): array
    {
        $db = App::getInstance()->db();
        $where = 'rule_code = ? AND is_active = 1';
        $params = [$ruleCode];
        if ($branchId !== null) {
            $where .= ' AND branch_id = ?';
            $params[] = $branchId;
        } else {
            $where .= ' AND branch_id IS NULL';
        }
        return $db->select("SELECT * FROM business_rules WHERE {$where} ORDER BY effective_from DESC", $params);
    }

    public static function override(string $ruleCode, string $entityType, int $entityId, string $overrideValueJson, string $reason): void
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $rule = $db->selectOne("SELECT id FROM business_rules WHERE rule_code = ? AND branch_id IS NULL AND is_active = 1", [$ruleCode]);
        if (!$rule) {
            throw new \RuntimeException("Rule not found: {$ruleCode}");
        }

        $db->insert('rule_overrides', [
            'rule_id'             => (int) $rule['id'],
            'entity_type'         => $entityType,
            'entity_id'           => $entityId,
            'override_value_json' => $overrideValueJson,
            'reason'              => $reason,
            'approved_by'         => $employee ? (int) $employee->id : null,
            'effective_from'      => date('Y-m-d'),
            'is_active'           => 1,
            'created_at'          => date('Y-m-d H:i:s'),
        ]);
    }

    public static function getEffective(string $ruleCode, string $entityType, int $entityId, ?int $branchId = null): mixed
    {
        $db = App::getInstance()->db();
        $rule = $db->selectOne("SELECT id FROM business_rules WHERE rule_code = ? AND branch_id IS NULL AND is_active = 1", [$ruleCode]);
        if ($rule) {
            $override = RuleOverride::findOverride((int) $rule['id'], $entityType, $entityId);
            if ($override) {
                return json_decode($override['override_value_json'], true);
            }
        }
        return self::get($ruleCode, $branchId);
    }

    public static function require(string $ruleCode, ?int $branchId = null): array
    {
        $data = self::get($ruleCode, $branchId);
        if ($data === null) {
            Logger::error("Missing required business rule: {$ruleCode}", ['branch_id' => $branchId]);
            throw new \RuntimeException("القاعدة '{$ruleCode}' غير مُعرّفة في النظام — يرجى مراجعة إعدادات القواعد");
        }
        return $data;
    }

    public static function requireValue(string $ruleCode, string $key = 'value', ?int $branchId = null): mixed
    {
        $data = self::require($ruleCode, $branchId);
        return $data[$key] ?? ($data['percentage'] ?? ($data['amount'] ?? ($data['value'] ?? null)));
    }

    public static function clearCache(): void
    {
        self::$cache = [];
    }
}