<?php
defined('BASEPATH') or exit('No direct script access allowed');

class SignatureAuth
{
    /** Max clock skew (seconds) */
    const MAX_SKEW = 300;

    /** Verify HMAC proof-of-possession for each request. */
    public function verify(int $adminId): bool
    {
        $CI =& get_instance();

        // Required headers
        $clientId = $this->hdr('X-Client-Id');
        $time     = (int)$this->hdr('X-Time');
        $nonce    = $this->hdr('X-Nonce');
        $sig      = $this->hdr('X-Signature');
        $bodyH    = $this->hdr('X-Body-SHA256') ?: '-';

        if (!$clientId || !$time || !$nonce || !$sig) return false;
        if (abs(time() - $time) > self::MAX_SKEW) return false;

        // Prevent replay: nonce must be unique for (clientId, time window)
        if ($this->nonceSeen($clientId, $nonce)) return false;
        $this->rememberNonce($clientId, $nonce);

        // Look up per-device secret (bind device to admin/user)
        $secret = $this->lookupDeviceSecret($adminId, $clientId);
        if (!$secret) return false;

        // Canonical request parts
        $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $path   = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
        $query  = $_SERVER['QUERY_STRING'] ?? '';
        $raw    = file_get_contents('php://input') ?: '';
        $calcBH = $raw ? hash('sha256', $raw) : '-';
        if ($bodyH !== $calcBH) return false;

        $msg = $method."\n".$path."\n".$query."\n".$time."\n".$nonce."\n".$bodyH;
        $calc = base64_encode(hash_hmac('sha256', $msg, $secret, true));

        return hash_equals($calc, $sig);
    }

    /** ==== Helpers ==== */

    private function hdr(string $name): ?string
    {
        $CI =& get_instance();
        return $CI->input->get_request_header($name, TRUE) ?? null;
    }

    private function lookupDeviceSecret(int $adminId, string $clientId): ?string
    {
        // TODO: replace with your own model/DB call.
        // Example table: device_keys(admin_id INT, device_id VARCHAR, secret VARCHAR, status ENUM('active','revoked'))
        $CI =& get_instance();
        $row = $CI->db->select('secret')
            ->where(['admin_id' => $adminId, 'device_id' => $clientId, 'status' => 'active'])
            ->get('device_keys')->row();
        return $row ? $row->secret : null;
    }

    private function nonceSeen(string $clientId, string $nonce): bool
    {
        // Use CI Cache driver (Redis/Memcached). Fallback to APCu if needed.
        $CI =& get_instance();
        if (!isset($CI->cache)) $CI->load->driver('cache', ['adapter' => 'redis', 'backup' => 'file']);
        $key = "nonce:{$clientId}:".hash('sha256', $nonce);
        $exists = $CI->cache->get($key);
        return (bool)$exists;
    }

    private function rememberNonce(string $clientId, string $nonce): void
    {
        $CI =& get_instance();
        if (!isset($CI->cache)) $CI->load->driver('cache', ['adapter' => 'redis', 'backup' => 'file']);
        $key = "nonce:{$clientId}:".hash('sha256', $nonce);
        // keep ~10 minutes
        $CI->cache->save($key, 1, 600);
    }
}
