<?php
declare(strict_types=1);

namespace App\ObjectModel;

use App\Core\Database;
use InvalidArgumentException;
use PDO;

final class ObjectRepository
{
    private PDO $pdo;

    public function __construct()
    {
        $this->pdo = Database::connection();
    }

    public function entities(): array
    {
        return $this->pdo->query("SELECT id, code, label, module_code FROM axiom_meta_entities WHERE active=1 AND archived_at IS NULL ORDER BY label")
            ->fetchAll() ?: [];
    }

    public function objects(?int $entityId = null, string $search = ''): array
    {
        $sql = "SELECT o.*, e.label AS entity_label, e.code AS entity_code,
                       (SELECT COUNT(*) FROM axiom_object_events ev WHERE ev.object_uuid=o.uuid) AS event_count
                FROM axiom_objects o
                INNER JOIN axiom_meta_entities e ON e.id=o.entity_id
                WHERE o.archived_at IS NULL";
        $params = [];
        if ($entityId !== null && $entityId > 0) {
            $sql .= ' AND o.entity_id=?';
            $params[] = $entityId;
        }
        if ($search !== '') {
            $sql .= ' AND (o.reference LIKE ? OR o.label LIKE ? OR o.status LIKE ?)';
            $needle = '%' . $search . '%';
            array_push($params, $needle, $needle, $needle);
        }
        $sql .= ' ORDER BY o.updated_at DESC, o.id DESC LIMIT 250';
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll() ?: [];
    }

    public function object(string $uuid): ?array
    {
        $stmt = $this->pdo->prepare("SELECT o.*, e.label AS entity_label, e.code AS entity_code
                                    FROM axiom_objects o
                                    INNER JOIN axiom_meta_entities e ON e.id=o.entity_id
                                    WHERE o.uuid=?");
        $stmt->execute([$uuid]);
        $row = $stmt->fetch();
        return $row ?: null;
    }

    public function events(string $uuid): array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM axiom_object_events WHERE object_uuid=? ORDER BY occurred_at DESC, id DESC');
        $stmt->execute([$uuid]);
        return $stmt->fetchAll() ?: [];
    }

    public function save(array $input, ?int $userId): string
    {
        $entityId = max(0, (int)($input['entity_id'] ?? 0));
        $reference = strtoupper(trim((string)($input['reference'] ?? '')));
        $label = trim((string)($input['label'] ?? ''));
        $status = trim((string)($input['status'] ?? 'draft')) ?: 'draft';
        $payload = trim((string)($input['payload_json'] ?? ''));
        $uuid = trim((string)($input['uuid'] ?? ''));

        if ($entityId <= 0 || $label === '') {
            throw new InvalidArgumentException('L’entité métier et le libellé sont obligatoires.');
        }
        if ($reference === '') {
            $reference = 'OBJ-' . date('Ymd-His') . '-' . strtoupper(substr(bin2hex(random_bytes(3)), 0, 6));
        }
        if ($payload === '') {
            $payload = '{}';
        }
        json_decode($payload, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new InvalidArgumentException('Les données JSON de l’objet sont invalides.');
        }

        $this->pdo->beginTransaction();
        try {
            if ($uuid !== '') {
                $existing = $this->object($uuid);
                if (!$existing) {
                    throw new InvalidArgumentException('Objet AXIOM introuvable.');
                }
                $version = ((int)$existing['version_no']) + 1;
                $stmt = $this->pdo->prepare('UPDATE axiom_objects SET entity_id=?,reference=?,label=?,status=?,payload_json=?,version_no=?,updated_by=?,updated_at=NOW() WHERE uuid=?');
                $stmt->execute([$entityId,$reference,$label,$status,$payload,$version,$userId,$uuid]);
                $event = 'updated';
            } else {
                $uuid = $this->uuidV4();
                $version = 1;
                $stmt = $this->pdo->prepare('INSERT INTO axiom_objects(uuid,entity_id,reference,label,status,payload_json,version_no,created_by,updated_by) VALUES(?,?,?,?,?,?,?,?,?)');
                $stmt->execute([$uuid,$entityId,$reference,$label,$status,$payload,$version,$userId,$userId]);
                $event = 'created';
            }

            $this->recordVersion($uuid, $version, $payload, $status, $userId);
            $this->recordEvent($uuid, $event, $event === 'created' ? 'Objet créé' : 'Objet modifié', $userId, ['reference' => $reference, 'version' => $version]);
            $this->pdo->commit();
            return $uuid;
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $e;
        }
    }

    public function archive(string $uuid, ?int $userId): void
    {
        $this->pdo->beginTransaction();
        try {
            $stmt = $this->pdo->prepare("UPDATE axiom_objects SET status='archived', archived_at=NOW(), updated_by=?, updated_at=NOW() WHERE uuid=? AND archived_at IS NULL");
            $stmt->execute([$userId, $uuid]);
            $this->recordEvent($uuid, 'archived', 'Objet archivé', $userId, []);
            $this->pdo->commit();
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    private function recordVersion(string $uuid, int $version, string $payload, string $status, ?int $userId): void
    {
        $stmt = $this->pdo->prepare('INSERT INTO axiom_object_versions(object_uuid,version_no,status,payload_json,created_by) VALUES(?,?,?,?,?)');
        $stmt->execute([$uuid,$version,$status,$payload,$userId]);
    }

    private function recordEvent(string $uuid, string $type, string $label, ?int $userId, array $data): void
    {
        $stmt = $this->pdo->prepare('INSERT INTO axiom_object_events(object_uuid,event_type,label,data_json,actor_user_id) VALUES(?,?,?,?,?)');
        $stmt->execute([$uuid,$type,$label,json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),$userId]);
    }

    private function uuidV4(): string
    {
        $data = random_bytes(16);
        $data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
        $data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
    }
}
