<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Service;

use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Psr\Log\LoggerInterface;

/**
 * Anti-tampering service for Deal Flow license enforcement.
 *
 * Provides two layers of protection:
 * 1. License integrity hash — verifies the stored license status was set by a genuine activation
 *    (not manually edited in the config). Hash = SHA256(key|subdomain|salt).
 * 2. File integrity checksum — detects modifications to critical plugin files. Advisory only
 *    (warns but does not disable) since legitimate updates also change files.
 */
class IntegrityService
{
    /**
     * Salt baked into the code. Used in the license integrity hash so that
     * knowing the key + subdomain alone is not enough to forge the hash.
     */
    private const SALT = 'df_v1_897ac44b437d91459e9bcda82805e26e';

    /** @var list<string> Critical files relative to the bundle root. */
    private const CRITICAL_FILES = [
        'Config/config.php',
        'EventSubscriber/ConfigSubscriber.php',
        'EventSubscriber/LicenseMenuSubscriber.php',
        'EventSubscriber/LicenseRouteGuard.php',
        'EventSubscriber/LicenseValidationSubscriber.php',
        'Service/IntegrityService.php',
        'Service/LicenseService.php',
    ];

    private readonly string $bundleDir;

    public function __construct(
        private readonly CoreParametersHelper $params,
        private readonly LoggerInterface $logger,
    ) {
        // Resolve bundle directory from this file's location
        $this->bundleDir = \dirname(__DIR__);
    }

    /**
     * Verify the license is genuinely valid — not just a config flag set to 'active'.
     * Combines license key + subdomain + salt into a verification hash and compares
     * against the stored hash that was set during activation.
     */
    public function verifyLicenseIntegrity(string $key, string $subdomain, string $storedHash): bool
    {
        if ($key === '' || $subdomain === '' || $storedHash === '') {
            return false;
        }

        $expected = $this->generateLicenseHash($key, $subdomain);

        return hash_equals($expected, $storedHash);
    }

    /**
     * Generate the integrity hash during activation.
     * Called by ConfigSubscriber when activation succeeds.
     */
    public function generateLicenseHash(string $key, string $subdomain): string
    {
        return hash('sha256', $key.'|'.$subdomain.'|'.self::SALT);
    }

    /**
     * Calculate checksums of critical PHP files.
     * Returns a hash of hashes — if any file is modified, this changes.
     */
    public function calculateFileChecksum(): string
    {
        $files = $this->getCriticalFiles();
        $hashes = [];

        foreach ($files as $relativePath) {
            $fullPath = $this->bundleDir.'/'.$relativePath;

            if (!is_file($fullPath) || !is_readable($fullPath)) {
                $this->logger->warning('Deal Flow integrity: critical file missing or unreadable: '.$relativePath);
                $hashes[] = 'missing:'.$relativePath;

                continue;
            }

            $hashes[] = hash_file('sha256', $fullPath);
        }

        return hash('sha256', implode('|', $hashes));
    }

    /**
     * Verify file integrity against stored checksum.
     *
     * Returns true if the checksum matches OR if no checksum is stored yet
     * (first run after install, before activation).
     */
    public function verifyFileIntegrity(): bool
    {
        $storedChecksum = (string) $this->params->get('dealflow_file_checksum', '');

        // No stored checksum yet — nothing to compare against
        if ($storedChecksum === '') {
            return true;
        }

        $currentChecksum = $this->calculateFileChecksum();

        if (!hash_equals($storedChecksum, $currentChecksum)) {
            $this->logger->warning(
                'Deal Flow integrity: file checksum mismatch — critical files may have been modified.'
            );

            return false;
        }

        return true;
    }

    /**
     * Get list of critical files to checksum (sorted alphabetically for deterministic hashing).
     *
     * @return list<string>
     */
    private function getCriticalFiles(): array
    {
        $files = self::CRITICAL_FILES;
        sort($files);

        return $files;
    }
}
