<?php
declare(strict_types=1);
namespace App\Platform\Soc\Correlation;

use App\Core\Database;
use PDO;

final class CorrelationEngine
{
    public function __construct(
        private readonly IncidentManager $incidents = new IncidentManager(),
        private readonly RiskScoreCalculator $scores = new RiskScoreCalculator(),
    ) {}

    /** @return array{rules:int,incidents:int,events:int} */
    public function run(int $batchSize = 500): array
    {
        $batchSize=max(10,min(2000,$batchSize));
        $rules=$this->activeRules();
        $incidentCount=0; $eventCount=0;
        foreach ($rules as $rule) {
            $events=$this->candidateEvents($rule,$batchSize);
            if (count($events) < (int)$rule['threshold_count']) continue;
            foreach ($this->groupEvents($events,(string)$rule['group_by']) as $key=>$group) {
                if (count($group) < (int)$rule['threshold_count']) continue;
                $score=$this->scores->calculate($group,(int)$rule['base_score']);
                $severity=$this->scores->severity($score);
                $uuid=$this->incidents->createOrMerge($rule,$group,$score,$severity,$key);
                $this->recordCorrelation($uuid,(int)$rule['id'],$key,count($group),$score);
                $incidentCount++; $eventCount+=count($group);
            }
        }
        return ['rules'=>count($rules),'incidents'=>$incidentCount,'events'=>$eventCount];
    }

    /** @return list<array<string,mixed>> */
    private function activeRules(): array
    {
        $statement=Database::connection()->prepare(
            'SELECT id,code,name,description,event_category,event_action,event_result,window_minutes,threshold_count,group_by,base_score '
            . 'FROM system_security_rules WHERE enabled=1 ORDER BY priority ASC,id ASC'
        );
        $statement->execute();
        return $statement->fetchAll() ?: [];
    }

    /** @param array<string,mixed> $rule @return list<array<string,mixed>> */
    private function candidateEvents(array $rule,int $limit): array
    {
        $statement=Database::connection()->prepare(
            'SELECT event_uuid,severity,category,module,action,result,user_id,organisation_id,session_reference,ip_hash,occurred_at '
            . 'FROM system_security_events WHERE occurred_at>=DATE_SUB(NOW(),INTERVAL :window_minutes MINUTE) '
            . 'AND category=:category AND (:event_action=\'\' OR action=:event_action_match) '
            . 'AND (:event_result=\'\' OR result=:event_result_match) ORDER BY occurred_at ASC LIMIT :row_limit'
        );
        $statement->bindValue('window_minutes',(int)$rule['window_minutes'],PDO::PARAM_INT);
        $statement->bindValue('category',(string)$rule['event_category']);
        $statement->bindValue('event_action',(string)$rule['event_action']);
        $statement->bindValue('event_action_match',(string)$rule['event_action']);
        $statement->bindValue('event_result',(string)$rule['event_result']);
        $statement->bindValue('event_result_match',(string)$rule['event_result']);
        $statement->bindValue('row_limit',$limit,PDO::PARAM_INT);
        $statement->execute();
        return $statement->fetchAll() ?: [];
    }

    /** @param list<array<string,mixed>> $events @return array<string,list<array<string,mixed>>> */
    private function groupEvents(array $events,string $groupBy): array
    {
        $groups=[];
        foreach ($events as $event) {
            $value = match ($groupBy) {
                'user' => (string)($event['user_id'] ?? 'anonymous'),
                'session' => (string)($event['session_reference'] ?? 'none'),
                'module' => (string)($event['module'] ?? 'unknown'),
                default => (string)($event['ip_hash'] ?? 'none'),
            };
            $key=hash('sha256',$groupBy.'|'.$value);
            $groups[$key][]=$event;
        }
        return $groups;
    }

    private function recordCorrelation(string $incidentUuid,int $ruleId,string $key,int $eventCount,int $score): void
    {
        $statement=Database::connection()->prepare(
            'INSERT INTO system_security_correlations '
            . '(correlation_uuid,incident_uuid,rule_id,correlation_key,event_count,risk_score,created_at) '
            . 'VALUES (:correlation_uuid,:incident_uuid,:rule_id,:correlation_key,:event_count,:risk_score,NOW())'
        );
        $statement->execute([
            'correlation_uuid'=>self::uuidV4(),'incident_uuid'=>$incidentUuid,'rule_id'=>$ruleId,
            'correlation_key'=>$key,'event_count'=>$eventCount,'risk_score'=>$score,
        ]);
    }

    private static function uuidV4(): string
    {
        $bytes=random_bytes(16); $bytes[6]=chr((ord($bytes[6])&15)|64); $bytes[8]=chr((ord($bytes[8])&63)|128);
        $hex=bin2hex($bytes);
        return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4).'-'.substr($hex,16,4).'-'.substr($hex,20);
    }
}
