<?php

namespace App\Services;

use CodeIgniter\Database\BaseConnection;
use DateTime;
use DateTimeZone;
use InvalidArgumentException;
use Throwable;

class AuditLogService
{
    private const TABLE_MAP = [
        'logpatient' => 'logpatient',
        'patient'    => 'logpatient',
        'visit'      => 'logpatient',
        'logorder'   => 'logorder',
        'order'      => 'logorder',
        'specimen'   => 'logorder',
        'result'     => 'logorder',
        'logmaster'  => 'logmaster',
        'master'     => 'logmaster',
        'config'     => 'logmaster',
        'valueset'   => 'logmaster',
        'logsystem'  => 'logsystem',
        'system'     => 'logsystem',
        'auth'       => 'logsystem',
        'job'        => 'logsystem',
    ];

    private const PRIMARY_KEYS = [
        'logpatient' => 'LogPatientID',
        'logorder'   => 'LogOrderID',
        'logmaster'  => 'LogMasterID',
        'logsystem'  => 'LogSystemID',
    ];

    private const DEFAULT_PAGE = 1;
    private const DEFAULT_PER_PAGE = 20;
    private const MAX_PER_PAGE = 100;

    private static ?BaseConnection $db = null;

    public function fetchLogs(array $filters): array
    {
        $tableKey = $filters['table'] ?? null;
        if (empty($tableKey)) {
            throw new InvalidArgumentException('table parameter is required');
        }

        $logTable = $this->resolveLogTable($tableKey);
        if ($logTable === null) {
            throw new InvalidArgumentException("Unknown audit table: {$tableKey}");
        }

        $builder = $this->getDb()->table($logTable);

        $this->applyFilters($builder, $filters);

        $total = (int) $builder->countAllResults(false);

        $page = $this->normalizePage($filters['page'] ?? null);
        $perPage = $this->normalizePerPage($filters['perPage'] ?? $filters['per_page'] ?? null);
        $offset = ($page - 1) * $perPage;

        $builder->orderBy('LogDate', 'DESC');
        $builder->orderBy($this->getPrimaryKey($logTable), 'DESC');

        $rows = $builder
            ->limit($perPage, $offset)
            ->get()
            ->getResultArray();

        return [
            'data' => $rows,
            'pagination' => [
                'page' => $page,
                'perPage' => $perPage,
                'total' => $total,
            ],
        ];
    }

    private function applyFilters($builder, array $filters): void
    {
        if (!empty($filters['rec_id'])) {
            $builder->where('RecID', (string) $filters['rec_id']);
        }

        if (!empty($filters['event_id'])) {
            $builder->where('EventID', $this->normalizeCode($filters['event_id']));
        }

        if (!empty($filters['activity_id'])) {
            $builder->where('ActivityID', $this->normalizeCode($filters['activity_id']));
        }

        $this->applyDateRange($builder, $filters['from'] ?? null, $filters['to'] ?? null);

        if (!empty($filters['search'])) {
            $search = trim($filters['search']);
            if ($search !== '') {
                $builder->groupStart();
                $builder->like('UserID', $search);
                $builder->orLike('Reason', $search);
                $builder->orLike('FldName', $search);
                $builder->orLike('FldValuePrev', $search);
                $builder->orLike('FldValueNew', $search);
                $builder->orLike('EventID', $search);
                $builder->orLike('ActivityID', $search);
                $builder->groupEnd();
            }
        }
    }

    private function applyDateRange($builder, ?string $from, ?string $to): void
    {
        if ($from !== null && trim($from) !== '') {
            $builder->where('LogDate >=', $this->normalizeDate($from));
        }
        if ($to !== null && trim($to) !== '') {
            $builder->where('LogDate <=', $this->normalizeDate($to));
        }
    }

    private function normalizeDate(string $value): string
    {
        try {
            $dt = new DateTime($value, new DateTimeZone('UTC'));
        } catch (Throwable $e) {
            throw new InvalidArgumentException('Invalid date: ' . $value);
        }

        return $dt->format('Y-m-d H:i:s');
    }

    private function normalizeCode(string $value): string
    {
        return strtoupper(trim($value));
    }

    private function normalizePage($value): int
    {
        $page = (int) ($value ?? self::DEFAULT_PAGE);
        return $page < 1 ? self::DEFAULT_PAGE : $page;
    }

    private function normalizePerPage($value): int
    {
        $perPage = (int) ($value ?? self::DEFAULT_PER_PAGE);
        if ($perPage < 1) {
            return self::DEFAULT_PER_PAGE;
        }
        if ($perPage > self::MAX_PER_PAGE) {
            throw new InvalidArgumentException('perPage cannot be greater than ' . self::MAX_PER_PAGE);
        }
        return $perPage;
    }

    private function resolveLogTable(?string $key): ?string
    {
        if ($key === null) {
            return null;
        }

        $lookup = strtolower(trim($key));
        return self::TABLE_MAP[$lookup] ?? null;
    }

    private function getPrimaryKey(string $table): string
    {
        return self::PRIMARY_KEYS[$table] ?? 'LogID';
    }

    private function getDb(): BaseConnection
    {
        return self::$db ??= \Config\Database::connect();
    }
}
