<?php

class SystemSettings {
    private $settingsFile;
    private $conn;
    private $tableReady = false;
    private $defaults = [
        'vat_percentage'              => 17.5,
        'profit_percentage'           => 30.0,
        'company_name'                => 'Company Name',
        'company_logo'                => '/IMS/assets/images/settings/company-logo.png',
        'currency_code'               => 'MWK',
        'timezone'                    => 'Africa/Blantyre',
        'maintenance_mode'            => false,
        'maintenance_branch_id'       => 0,
        'eis_enabled'                 => true,
        'eis_offline_mode'            => false,
        'eis_offline_since'           => null,
        'ims_only'                    => false,
        'allow_manual_sale_date'      => false,
        'service_body'                => 'Dealers in: IT Consumables & Electricals, Desktop Computers, Laptops, Projectors, Computer Accessories, Digital Home Theaters, Stereo Headphones, Household Furniture\'s & many more',
        // EIS API configuration codes
        'eis_environment'             => 'sandbox',
        'eis_api_url_sandbox'         => 'https://dev-eis-api.mra.mw/api/v1',
        'eis_api_url_production'      => 'https://eis-api.mra.mw/api/v1',
        'eis_validation_url_sandbox'  => 'https://dev-eis-portal.mra.mw/ReceiptValidation/Validate/',
        'eis_validation_url_production' => 'https://eis-portal.mra.mw/ReceiptValidation/Validate/',
        'eis_default_tax_rate_id'     => 'A',
        'eis_zero_rated_tax_ids'      => 'B,E,VAT_ZERO,ZERO,ZERO_RATED,EXEMPT',
        'eis_default_payment_method'  => 'CASH',
        'eis_warehouse_branch_id'     => 7,
        'eis_default_transfer_terminal_id' => 0,
    ];

    public function __construct($settingsFile = null, ?PDO $conn = null) {
        // Keep the old file as a one-time migration/fallback source. All writes now
        // go to system_configurations so every web page and worker sees one value.
        if ($settingsFile instanceof PDO && $conn === null) {
            $conn = $settingsFile;
            $settingsFile = null;
        }
        $this->settingsFile = $settingsFile ?: __DIR__ . '/../config/system_settings.php';
        $this->conn = $conn;
    }

    public function all() {
        $legacySettings = [];
        if (is_file($this->settingsFile)) {
            $loaded = require $this->settingsFile;
            if (is_array($loaded)) {
                $legacySettings = $loaded;
            }
        }

        $settings = array_merge($this->defaults, $this->onlySystemKeys($legacySettings));
        try {
            $this->ensureTable($legacySettings);
            $stmt = $this->conn->query('SELECT setting_key, setting_value, value_type FROM system_configurations');
            foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
                if (array_key_exists($row['setting_key'], $this->defaults)) {
                    $settings[$row['setting_key']] = $this->decodeValue($row['setting_value'], $row['value_type']);
                }
            }
        } catch (Throwable $e) {
            // Database startup must still be able to show a useful error page. Reads
            // fall back to the legacy file/defaults; saves deliberately fail below.
            error_log('System configuration read failed: ' . $e->getMessage());
        }

        $this->applyTimezone($settings['timezone']);

        return $settings;
    }

    public function get($key, $default = null) {
        $settings = $this->all();
        return array_key_exists($key, $settings) ? $settings[$key] : $default;
    }

    public function profitRate() {
        return ((float)$this->get('profit_percentage', $this->defaults['profit_percentage'])) / 100;
    }

    public function profitPercentage() {
        return (float)$this->get('profit_percentage', $this->defaults['profit_percentage']);
    }

    public function vatRate() {
        return ((float)$this->get('vat_percentage', $this->defaults['vat_percentage'])) / 100;
    }

    public function vatPercentage() {
        return (float)$this->get('vat_percentage', $this->defaults['vat_percentage']);
    }

    public function settingMetadata($key) {
        try {
            $this->ensureTable();
            $stmt = $this->conn->prepare('SELECT setting_key, value_type, source, description, updated_at FROM system_configurations WHERE setting_key = :setting_key');
            $stmt->execute([':setting_key' => (string)$key]);
            return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
        } catch (Throwable $e) {
            return null;
        }
    }

    /** Update the system-wide standard VAT rate from a downloaded MRA configuration. */
    public function updateVatFromMra($percentage) {
        if ($this->imsOnly()) {
            return $this->vatPercentage();
        }
        $percentage = $this->normalizePercentage($percentage, 'MRA VAT percentage');
        $this->ensureTable();
        $this->upsertSetting(
            'vat_percentage',
            $percentage,
            'mra',
            'Standard VAT percentage supplied by the latest MRA EIS global configuration.'
        );
        return $percentage;
    }

    public function eisEnabled() {
        return (bool)$this->get('eis_enabled', $this->defaults['eis_enabled']);
    }

    public function eisOfflineMode() {
        return (bool)$this->get('eis_offline_mode', false);
    }

    public function eisOfflineSince() {
        $value = $this->get('eis_offline_since');
        return is_string($value) && trim($value) !== '' ? trim($value) : null;
    }

    public function imsOnly() {
        return (bool)$this->get('ims_only', false);
    }

    public function allowManualSaleDate() {
        return (bool)$this->get('allow_manual_sale_date', false);
    }

    public function maintenanceMode() {
        return (bool)$this->get('maintenance_mode', false);
    }

    public function maintenanceBranchId() {
        return (int)$this->get('maintenance_branch_id', 0);
    }

    public function maintenanceAccessAllowed($role, $branchId) {
        if (!$this->maintenanceMode()) {
            return true;
        }

        $role = strtolower(trim((string)$role));
        if (in_array($role, ['admin', 'super_admin', 'controller'], true)) {
            return true;
        }

        $allowedBranchId = $this->maintenanceBranchId();
        return $allowedBranchId > 0 && (int)$branchId === $allowedBranchId;
    }

    public function eisEnvironment() {
        $v = strtolower(trim((string)$this->get('eis_environment', 'sandbox')));
        return $v === 'production' ? 'production' : 'sandbox';
    }

    public function eisApiUrl() {
        $env = $this->eisEnvironment();
        return rtrim((string)$this->get('eis_api_url_' . $env,
            $env === 'production'
                ? 'https://eis-api.mra.mw/api/v1'
                : 'https://dev-eis-api.mra.mw/api/v1'), '/');
    }

    public function eisValidationPortalUrl() {
        $env = $this->eisEnvironment();
        return rtrim((string)$this->get('eis_validation_url_' . $env,
            $env === 'production'
                ? 'https://eis-portal.mra.mw/ReceiptValidation/Validate/'
                : 'https://dev-eis-portal.mra.mw/ReceiptValidation/Validate/'), '/') . '/';
    }

    public function eisDefaultTaxRateId() {
        return strtoupper(trim((string)$this->get('eis_default_tax_rate_id', 'A'))) ?: 'A';
    }

    public function eisZeroRatedTaxIds() {
        $raw = (string)$this->get('eis_zero_rated_tax_ids', 'B,E,VAT_ZERO,ZERO,ZERO_RATED,EXEMPT');
        return array_values(array_filter(array_map(
            static fn($s) => strtoupper(trim($s)), explode(',', $raw)
        )));
    }

    public function eisDefaultPaymentMethod() {
        $v = strtoupper(trim((string)$this->get('eis_default_payment_method', 'CASH')));
        $allowed = ['CASH', 'CARD', 'MOBILE_MONEY', 'CHEQUE', 'BANK_TRANSFER'];
        return in_array($v, $allowed, true) ? $v : 'CASH';
    }

    public function eisWarehouseBranchId() {
        return (int)$this->get('eis_warehouse_branch_id', 7);
    }

    public function eisDefaultTransferTerminalId() {
        $v = (int)$this->get('eis_default_transfer_terminal_id', 0);
        return $v > 0 ? $v : null;
    }

    public function save(array $input) {
        return $this->saveSystemSettings($input);
    }

    public function saveSystemSettings(array $input) {
        $settings = $this->all();
        $settings['vat_percentage'] = $this->normalizePercentage(
            $input['vat_percentage'] ?? $settings['vat_percentage'],
            'VAT percentage'
        );
        $settings['profit_percentage'] = $this->normalizePercentage($input['profit_percentage'] ?? $settings['profit_percentage'], 'Profit percentage');
        $settings['company_name'] = $this->normalizeText($input['company_name'] ?? $settings['company_name'], 'Company name', 120);
        $settings['company_logo'] = $this->normalizeLogoPath($input['company_logo'] ?? $settings['company_logo']);
        $settings['currency_code'] = strtoupper($this->normalizeText($input['currency_code'] ?? $settings['currency_code'], 'Currency code', 10));
        $settings['timezone'] = $this->normalizeTimezone($input['timezone'] ?? $settings['timezone']);
        $settings['maintenance_mode'] = $this->normalizeBoolean($input['maintenance_mode'] ?? false);
        $settings['maintenance_branch_id'] = $this->normalizeOptionalNonNegativeInt($input['maintenance_branch_id'] ?? 0);
        if ($settings['maintenance_mode'] && $settings['maintenance_branch_id'] <= 0) {
            throw new InvalidArgumentException('Select the branch that may access the system during maintenance.');
        }
        $settings['eis_enabled'] = $this->normalizeBoolean($input['eis_enabled'] ?? false);
        $wasOffline = !empty($settings['eis_offline_mode']);
        $settings['eis_offline_mode'] = $settings['eis_enabled']
            && $this->normalizeBoolean($input['eis_offline_mode'] ?? false);
        if ($settings['eis_offline_mode'] && !$wasOffline) {
            $settings['eis_offline_since'] = (new DateTimeImmutable('now'))->format(DateTimeInterface::ATOM);
        } elseif (!$settings['eis_offline_mode']) {
            $settings['eis_offline_since'] = null;
        }
        // ims_only is only meaningful when EIS is disabled; force false when EIS is on
        $settings['ims_only'] = !$settings['eis_enabled']
            && $this->normalizeBoolean($input['ims_only'] ?? false);
        $settings['allow_manual_sale_date'] = $this->normalizeBoolean($input['allow_manual_sale_date'] ?? false);
        $settings['service_body'] = $this->normalizeOptionalText($input['service_body'] ?? $settings['service_body'], 1000);

        // EIS API configuration codes
        $settings['eis_environment']            = $this->normalizeEisEnvironment($input['eis_environment'] ?? $settings['eis_environment']);
        $settings['eis_api_url_sandbox']        = $this->normalizeEisUrl($input['eis_api_url_sandbox'] ?? $settings['eis_api_url_sandbox'], 'Sandbox API URL', false);
        $settings['eis_api_url_production']     = $this->normalizeEisUrl($input['eis_api_url_production'] ?? $settings['eis_api_url_production'], 'Production API URL', false);
        $settings['eis_validation_url_sandbox'] = $this->normalizeEisUrl($input['eis_validation_url_sandbox'] ?? $settings['eis_validation_url_sandbox'], 'Sandbox validation portal URL', true);
        $settings['eis_validation_url_production'] = $this->normalizeEisUrl($input['eis_validation_url_production'] ?? $settings['eis_validation_url_production'], 'Production validation portal URL', true);
        $settings['eis_default_tax_rate_id']    = $this->normalizeTaxRateCode($input['eis_default_tax_rate_id'] ?? $settings['eis_default_tax_rate_id']);
        $settings['eis_zero_rated_tax_ids']     = $this->normalizeZeroRatedIds($input['eis_zero_rated_tax_ids'] ?? $settings['eis_zero_rated_tax_ids']);
        $settings['eis_default_payment_method'] = $this->normalizePaymentMethod($input['eis_default_payment_method'] ?? $settings['eis_default_payment_method']);
        $settings['eis_warehouse_branch_id']    = $this->normalizePositiveInt($input['eis_warehouse_branch_id'] ?? $settings['eis_warehouse_branch_id'], 'Warehouse branch ID');
        $settings['eis_default_transfer_terminal_id'] = $this->normalizeOptionalNonNegativeInt($input['eis_default_transfer_terminal_id'] ?? $settings['eis_default_transfer_terminal_id']);

        return $this->write($settings);
    }

    private function onlySystemKeys(array $settings) {
        return array_intersect_key($settings, $this->defaults);
    }

    private function write(array $settings) {
        return $this->writeToDatabase($settings);
    }

    private function writeToDatabase(array $settings) {
        try {
            $this->ensureTable();
            $this->conn->beginTransaction();
            foreach ($this->onlySystemKeys($settings) as $key => $value) {
                $metadata = $key === 'vat_percentage' ? $this->settingMetadata($key) : null;
                if ($key === 'vat_percentage'
                    && ($metadata['source'] ?? '') === 'mra'
                    && empty($settings['ims_only'])) {
                    continue;
                }
                $this->upsertSetting($key, $value, 'admin');
            }
            $this->conn->commit();
        } catch (Throwable $e) {
            if ($this->conn instanceof PDO && $this->conn->inTransaction()) {
                $this->conn->rollBack();
            }
            throw new Exception('Unable to save system settings in the configuration table: ' . $e->getMessage(), 0, $e);
        }
        return $this->all();
    }

    private function ensureConnection() {
        if ($this->conn instanceof PDO) {
            return;
        }
        if (isset($GLOBALS['conn']) && $GLOBALS['conn'] instanceof PDO) {
            $this->conn = $GLOBALS['conn'];
            return;
        }
        $config = require __DIR__ . '/../config/database.php';
        $this->conn = new PDO(
            'mysql:host=' . $config['host'] . ';dbname=' . $config['db_name'] . ';charset=utf8mb4',
            $config['username'],
            $config['password'],
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
        );
    }

    private function ensureTable(array $legacySettings = []) {
        if ($this->tableReady && $this->conn instanceof PDO) {
            return;
        }
        $this->ensureConnection();
        $this->createConfigurationTable();
        $this->seedConfigurationTable($legacySettings);
        $this->tableReady = true;
    }

    private function createConfigurationTable() {
        $sql = 'CREATE TABLE IF NOT EXISTS system_configurations ('
            . 'setting_key VARCHAR(100) NOT NULL PRIMARY KEY,'
            . 'setting_value LONGTEXT NULL,'
            . 'value_type VARCHAR(20) NOT NULL DEFAULT \'string\','
            . 'source VARCHAR(30) NOT NULL DEFAULT \'system\','
            . 'description VARCHAR(255) DEFAULT NULL,'
            . 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,'
            . 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,'
            . 'KEY idx_system_configurations_source (source)'
            . ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci';
        $this->conn->exec($sql);
    }

    private function seedConfigurationTable(array $legacySettings) {
        $seed = array_merge($this->defaults, $this->onlySystemKeys($legacySettings));
        $stmt = $this->conn->prepare(
            'INSERT IGNORE INTO system_configurations (setting_key, setting_value, value_type, source, description) '
            . 'VALUES (:setting_key, :setting_value, :value_type, :source, :description)'
        );
        foreach ($seed as $key => $value) {
            [$encoded, $type] = $this->encodeValue($value);
            $stmt->execute([
                ':setting_key' => $key,
                ':setting_value' => $encoded,
                ':value_type' => $type,
                ':source' => array_key_exists($key, $legacySettings) ? 'legacy_file' : 'default',
                ':description' => $key === 'vat_percentage' ? 'System-wide standard VAT percentage.' : null,
            ]);
        }
    }

    private function upsertSetting($key, $value, $source, $description = null) {
        [$encoded, $type] = $this->encodeValue($value);
        $stmt = $this->conn->prepare(
            'INSERT INTO system_configurations (setting_key, setting_value, value_type, source, description) '
            . 'VALUES (:setting_key, :setting_value, :value_type, :source, :description) '
            . 'ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), value_type = VALUES(value_type), '
            . 'source = VALUES(source), description = COALESCE(VALUES(description), description)'
        );
        $stmt->execute([
            ':setting_key' => (string)$key,
            ':setting_value' => $encoded,
            ':value_type' => $type,
            ':source' => (string)$source,
            ':description' => $description,
        ]);
    }

    private function encodeValue($value) {
        if ($value === null) {
            return [null, 'null'];
        }
        if (is_bool($value)) {
            return [$value ? '1' : '0', 'boolean'];
        }
        if (is_int($value)) {
            return [(string)$value, 'integer'];
        }
        if (is_float($value)) {
            return [(string)$value, 'float'];
        }
        if (is_array($value)) {
            return [json_encode($value, JSON_UNESCAPED_SLASHES), 'json'];
        }
        return [(string)$value, 'string'];
    }

    private function decodeValue($value, $type) {
        switch ($type) {
            case 'null': return null;
            case 'boolean': return filter_var($value, FILTER_VALIDATE_BOOLEAN);
            case 'integer': return (int)$value;
            case 'float': return (float)$value;
            case 'json':
                $decoded = json_decode((string)$value, true);
                return is_array($decoded) ? $decoded : [];
            default: return (string)$value;
        }
    }

    private function normalizePercentage($value, $label) {
        if (!is_numeric($value)) {
            throw new InvalidArgumentException($label . ' must be a number.');
        }

        $value = (float)$value;
        if ($value < 0 || $value > 100) {
            throw new InvalidArgumentException($label . ' must be between 0 and 100.');
        }

        return round($value, 2);
    }

    private function normalizeText($value, $label, $maxLength) {
        $value = trim((string)$value);
        if ($value === '') {
            throw new InvalidArgumentException($label . ' cannot be empty.');
        }

        if (strlen($value) > $maxLength) {
            throw new InvalidArgumentException($label . ' is too long.');
        }

        return $value;
    }

    private function normalizeLogoPath($value) {
        $value = trim((string)$value);
        if ($value === '') {
            return $this->defaults['company_logo'];
        }

        if (strlen($value) > 255) {
            throw new InvalidArgumentException('Company logo path is too long.');
        }

        if (preg_match('/^(https?:)?\/\//i', $value)) {
            return $value;
        }

        if ($value[0] !== '/') {
            throw new InvalidArgumentException('Company logo path must start with / or be a full URL.');
        }

        return $value;
    }

    private function normalizeTimezone($value) {
        $value = trim((string)$value);
        if (!in_array($value, DateTimeZone::listIdentifiers(), true)) {
            throw new InvalidArgumentException('Invalid timezone selected.');
        }

        return $value;
    }

    private function normalizeOptionalText($value, $maxLength) {
        $value = trim((string)$value);
        if (strlen($value) > $maxLength) {
            throw new InvalidArgumentException('Service body is too long.');
        }

        return $value;
    }

    private function normalizeBoolean($value) {
        return filter_var($value, FILTER_VALIDATE_BOOLEAN);
    }

    private function applyTimezone($timezone) {
        if (in_array($timezone, DateTimeZone::listIdentifiers(), true)) {
            date_default_timezone_set($timezone);
        }
    }

    private function normalizeEisEnvironment($value) {
        $v = strtolower(trim((string)$value));
        return $v === 'production' ? 'production' : 'sandbox';
    }

    private function normalizeEisUrl($value, $label, $trailingSlash = false) {
        $value = trim((string)$value);
        if ($value === '') {
            throw new InvalidArgumentException($label . ' cannot be empty.');
        }
        if (strlen($value) > 300) {
            throw new InvalidArgumentException($label . ' is too long.');
        }
        if (!filter_var($value, FILTER_VALIDATE_URL)) {
            throw new InvalidArgumentException($label . ' must be a valid URL.');
        }
        return $trailingSlash ? rtrim($value, '/') . '/' : rtrim($value, '/');
    }

    private function normalizeTaxRateCode($value) {
        $value = strtoupper(trim((string)$value));
        if ($value === '' || strlen($value) > 20) {
            throw new InvalidArgumentException('Default tax rate code must be 1–20 characters.');
        }
        return $value;
    }

    private function normalizeZeroRatedIds($value) {
        $ids = array_values(array_filter(array_map(
            static fn($s) => strtoupper(trim($s)), explode(',', (string)$value)
        )));
        if (empty($ids)) {
            throw new InvalidArgumentException('Zero-rated tax IDs cannot be empty.');
        }
        return implode(',', $ids);
    }

    private function normalizePaymentMethod($value) {
        $v = strtoupper(trim((string)$value));
        $allowed = ['CASH', 'CARD', 'MOBILE_MONEY', 'CHEQUE', 'BANK_TRANSFER'];
        if (!in_array($v, $allowed, true)) {
            throw new InvalidArgumentException('Payment method must be one of: ' . implode(', ', $allowed) . '.');
        }
        return $v;
    }

    private function normalizePositiveInt($value, $label) {
        $v = (int)$value;
        if ($v < 1) {
            throw new InvalidArgumentException($label . ' must be a positive integer.');
        }
        return $v;
    }

    private function normalizeOptionalNonNegativeInt($value) {
        $v = (int)$value;
        return $v > 0 ? $v : 0;
    }
}
