<?php

namespace classes;

use objects\users_o;
use Throwable;

class system_search_economic_customer_index
{
    public const TABLE = 'system_search_economic_customer_index';

    private static bool $initialized = false;

    public static function ensureTable(): void
    {
        if (self::$initialized) {
            return;
        }

        global $db;
        if (!is_object($db) || !method_exists($db, 'query')) {
            return;
        }

        $sql = "CREATE TABLE IF NOT EXISTS `" . self::TABLE . "` (
            `customer_number` INT NOT NULL,
            `user_id` INT NULL,
            `local_display_name` VARCHAR(255) NULL,
            `local_email` VARCHAR(255) NULL,
            `local_phone` VARCHAR(64) NULL,
            `economic_name` VARCHAR(255) NULL,
            `economic_address` VARCHAR(255) NULL,
            `economic_city` VARCHAR(255) NULL,
            `economic_zip` VARCHAR(64) NULL,
            `economic_email` VARCHAR(255) NULL,
            `economic_cvr` VARCHAR(64) NULL,
            `economic_mobile_phone` VARCHAR(64) NULL,
            `economic_barred` TINYINT(1) NULL,
            `search_text` TEXT NULL,
            `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (`customer_number`),
            INDEX `idx_system_search_econ_customer_user` (`user_id`),
            INDEX `idx_system_search_econ_customer_name` (`economic_name`),
            INDEX `idx_system_search_econ_customer_email` (`economic_email`),
            INDEX `idx_system_search_econ_customer_cvr` (`economic_cvr`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";

        $db->query($sql);
        self::ensureColumn(
            'economic_barred',
            "ALTER TABLE `" . self::TABLE . "` ADD COLUMN `economic_barred` TINYINT(1) NULL AFTER `economic_mobile_phone`"
        );
        self::$initialized = true;
    }

    /**
     * @param array<int, int> $customerNumbers
     * @return array<int, array<string, mixed>>
     */
    public static function fetchContexts(array $customerNumbers): array
    {
        self::ensureTable();

        $normalized = array_values(array_unique(array_filter(
            array_map('intval', $customerNumbers),
            static fn(int $value): bool => $value > 0
        )));
        if (empty($normalized)) {
            return [];
        }

        global $db;
        if (!is_object($db) || !method_exists($db, 'query')) {
            return [];
        }

        $result = $db->query(
            "SELECT `customer_number`, `user_id`, `local_display_name`, `local_email`, `local_phone`,"
            . " `economic_name`, `economic_address`, `economic_city`, `economic_zip`, `economic_email`,"
            . " `economic_cvr`, `economic_mobile_phone`, `economic_barred`"
            . " FROM `" . self::TABLE . "`"
            . " WHERE `customer_number` IN (" . implode(',', $normalized) . ")"
        );
        if (!($result instanceof \mysqli_result)) {
            return [];
        }

        $contexts = [];
        while ($row = $result->fetch_assoc()) {
            $customerNumber = (int)($row['customer_number'] ?? 0);
            if ($customerNumber <= 0) {
                continue;
            }

            $barred = self::toNullableBool($row['economic_barred'] ?? null);
            $contexts[$customerNumber] = [
                'customer_number' => $customerNumber,
                'user_id' => self::toNullableInt($row['user_id'] ?? null),
                'name' => self::toNullableString($row['economic_name'] ?? null)
                    ?? self::toNullableString($row['local_display_name'] ?? null),
                'barred' => $barred,
                'status' => self::barredStatus($barred),
                'email' => self::toNullableString($row['economic_email'] ?? null)
                    ?? self::toNullableString($row['local_email'] ?? null),
                'phone' => self::toNullableString($row['economic_mobile_phone'] ?? null)
                    ?? self::toNullableString($row['local_phone'] ?? null),
                'cvr' => self::toNullableString($row['economic_cvr'] ?? null),
                'address' => self::toNullableString($row['economic_address'] ?? null),
                'city' => self::toNullableString($row['economic_city'] ?? null),
                'zip' => self::toNullableString($row['economic_zip'] ?? null),
            ];
        }

        return $contexts;
    }

    /**
     * Rebuild local e-conomic customer index from local users + cached/live e-conomic snapshots.
     *
     * @return array<string, int>
     */
    public static function refreshIndex(bool $refreshEconomicData = false): array
    {
        self::ensureTable();

        global $db;
        if (!is_object($db) || !method_exists($db, 'query') || !property_exists($db, 'conn')) {
            return [
                'processed' => 0,
                'upserted' => 0,
                'deleted' => 0,
                'errors' => 0,
            ];
        }

        $stats = [
            'processed' => 0,
            'upserted' => 0,
            'deleted' => 0,
            'errors' => 0,
        ];

        $result = $db->query("SELECT `id`, `customer_number`, `display_name`, `email`, `phone`
            FROM `users`
            WHERE `customer_number` IS NOT NULL
              AND `customer_number` <> 0");
        if (!($result instanceof \mysqli_result)) {
            return $stats;
        }

        $rows = $db->fetch_all($result);
        $seenCustomerNumbers = [];

        foreach ($rows as $row) {
            $stats['processed']++;

            $customerNumber = (int)($row['customer_number'] ?? 0);
            if ($customerNumber <= 0) {
                continue;
            }
            $seenCustomerNumbers[$customerNumber] = true;

            $economic = [];
            try {
                $tmpUser = new users_o();
                $tmpUser->getUserByCustomerNumber($customerNumber);
                if ($refreshEconomicData) {
                    $tmpUser->getCustomerEcocomicData($customerNumber);
                }
                $cached = $tmpUser->getCached('economic_customer');
                if (!$cached && !$refreshEconomicData) {
                    $tmpUser->getCustomerEcocomicData($customerNumber);
                    $cached = $tmpUser->getCached('economic_customer');
                }
                $economic = self::normalizeEconomicSnapshot($cached);
            } catch (Throwable) {
                $stats['errors']++;
            }

            $localDisplayName = self::toNullableString($row['display_name'] ?? null);
            $localEmail = self::toNullableString($row['email'] ?? null);
            $localPhone = self::toNullableString($row['phone'] ?? null);

            $economicName = self::toNullableString($economic['name'] ?? null);
            $economicAddress = self::toNullableString($economic['address'] ?? null);
            $economicCity = self::toNullableString($economic['city'] ?? null);
            $economicZip = self::toNullableString($economic['zip'] ?? null);
            $economicEmail = self::toNullableString($economic['email'] ?? null);
            $economicCvr = self::toNullableString($economic['corporateIdentificationNumber'] ?? null);
            $economicMobilePhone = self::toNullableString($economic['mobilePhone'] ?? null);
            $economicBarred = self::toNullableBool($economic['barred'] ?? null);

            $searchText = trim(implode(' ', array_values(array_filter([
                $customerNumber > 0 ? (string)$customerNumber : null,
                $localDisplayName,
                $localEmail,
                $localPhone,
                $economicName,
                $economicAddress,
                $economicCity,
                $economicZip,
                $economicEmail,
                $economicCvr,
                $economicMobilePhone,
            ], static fn($v) => is_string($v) && trim($v) !== ''))));
            if ($searchText === '') {
                $searchText = null;
            }

            $sql = "INSERT INTO `" . self::TABLE . "` (
                    `customer_number`,
                    `user_id`,
                    `local_display_name`,
                    `local_email`,
                    `local_phone`,
                    `economic_name`,
                    `economic_address`,
                    `economic_city`,
                    `economic_zip`,
                    `economic_email`,
                    `economic_cvr`,
                    `economic_mobile_phone`,
                    `economic_barred`,
                    `search_text`
                ) VALUES (
                    " . (int)$customerNumber . ",
                    " . (int)($row['id'] ?? 0) . ",
                    " . self::sqlNullableString($localDisplayName) . ",
                    " . self::sqlNullableString($localEmail) . ",
                    " . self::sqlNullableString($localPhone) . ",
                    " . self::sqlNullableString($economicName) . ",
                    " . self::sqlNullableString($economicAddress) . ",
                    " . self::sqlNullableString($economicCity) . ",
                    " . self::sqlNullableString($economicZip) . ",
                    " . self::sqlNullableString($economicEmail) . ",
                    " . self::sqlNullableString($economicCvr) . ",
                    " . self::sqlNullableString($economicMobilePhone) . ",
                    " . self::sqlNullableBool($economicBarred) . ",
                    " . self::sqlNullableString($searchText) . "
                ) ON DUPLICATE KEY UPDATE
                    `user_id` = VALUES(`user_id`),
                    `local_display_name` = VALUES(`local_display_name`),
                    `local_email` = VALUES(`local_email`),
                    `local_phone` = VALUES(`local_phone`),
                    `economic_name` = VALUES(`economic_name`),
                    `economic_address` = VALUES(`economic_address`),
                    `economic_city` = VALUES(`economic_city`),
                    `economic_zip` = VALUES(`economic_zip`),
                    `economic_email` = VALUES(`economic_email`),
                    `economic_cvr` = VALUES(`economic_cvr`),
                    `economic_mobile_phone` = VALUES(`economic_mobile_phone`),
                    `economic_barred` = VALUES(`economic_barred`),
                    `search_text` = VALUES(`search_text`),
                    `updated_at` = CURRENT_TIMESTAMP";
            $db->query($sql);
            $stats['upserted']++;
        }

        $seen = array_keys($seenCustomerNumbers);
        if (empty($seen)) {
            $db->query("DELETE FROM `" . self::TABLE . "`");
            $stats['deleted'] = self::safeAffectedRows();
            return $stats;
        }

        $in = implode(',', array_map('intval', $seen));
        $db->query("DELETE FROM `" . self::TABLE . "` WHERE `customer_number` NOT IN (" . $in . ")");
        $stats['deleted'] = self::safeAffectedRows();

        return $stats;
    }

    /**
     * @return array<string, mixed>
     */
    private static function normalizeEconomicSnapshot(mixed $snapshot): array
    {
        if (is_object($snapshot)) {
            return get_object_vars($snapshot);
        }
        if (is_array($snapshot)) {
            return $snapshot;
        }
        return [];
    }

    private static function toNullableString(mixed $value): ?string
    {
        if ($value === null) {
            return null;
        }
        $string = trim((string)$value);
        return $string === '' ? null : $string;
    }

    private static function toNullableInt(mixed $value): ?int
    {
        if (is_int($value)) {
            return $value;
        }
        if (is_numeric($value) && (string)(int)$value === trim((string)$value)) {
            return (int)$value;
        }
        return null;
    }

    private static function toNullableBool(mixed $value): ?bool
    {
        if ($value === null || $value === '') {
            return null;
        }
        if (is_bool($value)) {
            return $value;
        }
        if (is_int($value)) {
            return $value !== 0;
        }
        if (is_string($value)) {
            $normalized = trim(mb_strtolower($value));
            if ($normalized === '') {
                return null;
            }
            if (in_array($normalized, ['1', 'true', 'yes'], true)) {
                return true;
            }
            if (in_array($normalized, ['0', 'false', 'no'], true)) {
                return false;
            }
        }
        return null;
    }

    private static function sqlNullableString(?string $value): string
    {
        global $db;
        if ($value === null) {
            return 'NULL';
        }
        return "'" . $db->escape_string($value) . "'";
    }

    private static function sqlNullableBool(?bool $value): string
    {
        if ($value === null) {
            return 'NULL';
        }
        return $value ? '1' : '0';
    }

    private static function ensureColumn(string $column, string $alterSql): void
    {
        global $db;
        if (!is_object($db) || !method_exists($db, 'query')) {
            return;
        }

        $result = $db->query(
            "SHOW COLUMNS FROM `" . self::TABLE . "` LIKE '" . $db->escape_string($column) . "'"
        );
        if ($result instanceof \mysqli_result && $result->num_rows === 0) {
            $db->query($alterSql);
        }
    }

    private static function barredStatus(?bool $barred): string
    {
        return match ($barred) {
            true => 'barred',
            false => 'active',
            default => 'unknown',
        };
    }

    private static function safeAffectedRows(): int
    {
        global $db;
        if (!is_object($db) || !property_exists($db, 'conn') || !is_object($db->conn)) {
            return 0;
        }
        return max(0, (int)($db->conn->affected_rows ?? 0));
    }
}
