<?php
namespace EasyChat\Model;

class AppPairing extends Base {

    const DEFAULT_TTL_SECONDS = 600;
    const MAX_ATTEMPTS = 10;

    public function ensureTable() {
        $this->db->query(self::tableSql($this->prefix));
    }

    public static function tableSql($prefix) {
        return "CREATE TABLE IF NOT EXISTS `{$prefix}app_pairing_tokens` (
            `id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
            `public_id` VARCHAR(48) NOT NULL,
            `secret_hash` CHAR(64) NOT NULL,
            `code_hash` CHAR(64) NOT NULL,
            `manager_id` INT(11) NOT NULL,
            `expires_at` DATETIME NOT NULL,
            `used_at` DATETIME DEFAULT NULL,
            `created_at` DATETIME NOT NULL,
            `created_ip_hash` CHAR(64) DEFAULT NULL,
            `used_by_fcm_hash` CHAR(64) DEFAULT NULL,
            `attempts` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
            `last_error` VARCHAR(64) NOT NULL DEFAULT '',
            UNIQUE KEY `uniq_public_id` (`public_id`),
            UNIQUE KEY `uniq_secret_hash` (`secret_hash`),
            UNIQUE KEY `uniq_code_hash` (`code_hash`),
            KEY `idx_manager_expires` (`manager_id`, `expires_at`),
            KEY `idx_code_expires` (`code_hash`, `expires_at`),
            KEY `idx_used_at` (`used_at`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
    }

    public function create($manager_id, $ttl_seconds = self::DEFAULT_TTL_SECONDS, $created_ip = '') {
        $this->ensureTable();

        $manager_id = (int)$manager_id;
        $ttl_seconds = max(60, min(1800, (int)$ttl_seconds));
        $this->pruneExpired();

        $public_id = 'pair_' . bin2hex(random_bytes(12));
        $secret = 'pairsec_' . bin2hex(random_bytes(24));
        $compat_code_hash = hash('sha256', 'legacy_pairing_compat:' . bin2hex(random_bytes(24)));
        $created_ip_hash = $created_ip !== '' ? hash('sha256', (string)$created_ip) : null;

         
         
         
        $this->db->query("INSERT INTO `{$this->prefix}app_pairing_tokens` SET
            `public_id` = '" . $this->db->escape($public_id) . "',
            `secret_hash` = '" . $this->db->escape($this->hashSecret($secret)) . "',
            `code_hash` = '" . $this->db->escape($compat_code_hash) . "',
            `manager_id` = " . $manager_id . ",
            `expires_at` = DATE_ADD(NOW(), INTERVAL " . $ttl_seconds . " SECOND),
            `created_at` = NOW(),
            `created_ip_hash` = " . ($created_ip_hash ? "'" . $this->db->escape($created_ip_hash) . "'" : "NULL"));

        $query = $this->db->query("SELECT `expires_at` FROM `{$this->prefix}app_pairing_tokens`
            WHERE `public_id` = '" . $this->db->escape($public_id) . "'
            LIMIT 1");

        return array(
            'public_id' => $public_id,
            'secret' => $secret,
            'expires_at' => $query && $query->row ? (string)$query->row['expires_at'] : '',
        );
    }

    public function validateBySecret($public_id, $secret) {
        $this->ensureTable();
        $public_id = trim((string)$public_id);
        $secret_hash = $this->hashSecret($secret);
        if ($public_id === '' || $secret_hash === '') {
            return array('success' => false, 'error' => 'missing_pairing_secret');
        }

        $query = $this->db->query("SELECT *, (`expires_at` <= NOW()) AS `is_expired`
            FROM `{$this->prefix}app_pairing_tokens`
            WHERE `public_id` = '" . $this->db->escape($public_id) . "'
            LIMIT 1");

        if (!$query || !$query->num_rows) {
            return array('success' => false, 'error' => 'not_found');
        }

        if (!hash_equals((string)$query->row['secret_hash'], $secret_hash)) {
            $this->touchError((int)$query->row['id'], 'secret_mismatch', true);
            return array('success' => false, 'error' => 'not_found');
        }

        return $this->validateRow($query->row);
    }

    public function markUsed($id, $fcm_token) {
        $fcm_hash = $fcm_token !== '' ? hash('sha256', (string)$fcm_token) : null;
        $set_fcm = $fcm_hash
            ? ", `used_by_fcm_hash` = '" . $this->db->escape($fcm_hash) . "'"
            : "";

        $this->db->query("UPDATE `{$this->prefix}app_pairing_tokens`
            SET `used_at` = NOW(), `last_error` = ''{$set_fcm}
            WHERE `id` = " . (int)$id . "
              AND `used_at` IS NULL
            LIMIT 1");
    }

    




    public function consumeBySecret($public_id, $secret, $fcm_token = '') {
        $public_id = trim((string)$public_id);
        $secret_hash = $this->hashSecret($secret);
        if ($public_id === '' || $secret_hash === '') {
            return ['success' => false, 'error' => 'missing_pairing_secret'];
        }
        $query = $this->db->query(
            "SELECT *, (`expires_at` <= NOW()) AS `is_expired`
             FROM `{$this->prefix}app_pairing_tokens`
             WHERE `public_id` = '" . $this->db->escape($public_id) . "'
             LIMIT 1 FOR UPDATE"
        );
        if (!$query->num_rows || !hash_equals((string)$query->row['secret_hash'], $secret_hash)) {
            return ['success' => false, 'error' => 'not_found'];
        }
        $validated = $this->validateRow($query->row);
        if (empty($validated['success'])) {
            return $validated;
        }
        $fcm_hash = $fcm_token !== '' ? hash('sha256', (string)$fcm_token) : null;
        $this->db->query(
            "UPDATE `{$this->prefix}app_pairing_tokens` SET
                `used_at` = NOW(),
                `used_by_fcm_hash` = " . ($fcm_hash ? "'" . $this->db->escape($fcm_hash) . "'" : 'NULL') . ",
                `last_error` = ''
             WHERE `id` = " . (int)$query->row['id'] . " AND `used_at` IS NULL"
        );
        if ($this->db->countAffected() !== 1) {
            return ['success' => false, 'error' => 'already_used'];
        }
        return [
            'success' => true,
            'row' => $query->row,
            'manager_id' => (int)$query->row['manager_id'],
        ];
    }

    private function validateRow(array $row) {
        $id = (int)$row['id'];
        if ((int)$row['attempts'] >= self::MAX_ATTEMPTS) {
            $this->touchError($id, 'rate_limited', false);
            return array('success' => false, 'error' => 'rate_limited');
        }

        if (!empty($row['used_at'])) {
            $this->touchError($id, 'already_used', true);
            return array('success' => false, 'error' => 'already_used');
        }

        if ((int)$row['is_expired'] === 1) {
            $this->touchError($id, 'expired', true);
            return array('success' => false, 'error' => 'expired');
        }

        $this->touchError($id, '', false);
        return array(
            'success' => true,
            'row' => $row,
            'manager_id' => (int)$row['manager_id'],
        );
    }

    private function touchError($id, $reason, $count_attempt) {
        $attempt_sql = $count_attempt ? "`attempts` = `attempts` + 1, " : "";
        $this->db->query("UPDATE `{$this->prefix}app_pairing_tokens`
            SET {$attempt_sql}`last_error` = '" . $this->db->escape($reason) . "'
            WHERE `id` = " . (int)$id . "
            LIMIT 1");
    }

    private function pruneExpired() {
        $this->db->query("DELETE FROM `{$this->prefix}app_pairing_tokens`
            WHERE `expires_at` < DATE_SUB(NOW(), INTERVAL 7 DAY)
            LIMIT 500");
    }

    private function hashSecret($secret) {
        $secret = trim((string)$secret);
        return $secret === '' ? '' : hash('sha256', $secret);
    }
}
