<?php

namespace QUI\MasterLogin;

use DateTimeImmutable;
use QUI;
use QUI\Utils\Doctrine;
use QUI\Utils\Grid;

use function array_map;
use function explode;
use function in_array;
use function is_array;
use function is_scalar;
use function sprintf;
use function trim;

class Audit
{
    /**
     * Return Master Login audit entries for the backend grid.
     *
     * @param array<string, mixed> $searchParams
     * @return list<array<string, mixed>>|int
     */
    public static function search(array $searchParams): array | int
    {
        $Connection = QUI::getDataBaseConnection();
        $Platform = $Connection->getDatabasePlatform();
        $QueryBuilder = $Connection->createQueryBuilder();
        $auditTable = Doctrine::quoteIdentifier(Manager::getLogTable());
        $usersTable = Doctrine::quoteIdentifier(QUI\Users\Manager::table());

        $auditId = 'audit.' . $Platform->quoteSingleIdentifier('id');
        $masterId = 'audit.' . $Platform->quoteSingleIdentifier('master_id');
        $slaveId = 'audit.' . $Platform->quoteSingleIdentifier('slave_id');
        $start = 'audit.' . $Platform->quoteSingleIdentifier('start');
        $end = 'audit.' . $Platform->quoteSingleIdentifier('end');
        $masterUuid = 'master_user.' . $Platform->quoteSingleIdentifier('uuid');
        $slaveUuid = 'slave_user.' . $Platform->quoteSingleIdentifier('uuid');
        $masterUsername = 'master_user.' . $Platform->quoteSingleIdentifier('username');
        $slaveUsername = 'slave_user.' . $Platform->quoteSingleIdentifier('username');
        $masterFirstname = 'master_user.' . $Platform->quoteSingleIdentifier('firstname');
        $slaveFirstname = 'slave_user.' . $Platform->quoteSingleIdentifier('firstname');
        $masterLastname = 'master_user.' . $Platform->quoteSingleIdentifier('lastname');
        $slaveLastname = 'slave_user.' . $Platform->quoteSingleIdentifier('lastname');

        $QueryBuilder
            ->from($auditTable, 'audit')
            ->leftJoin(
                'audit',
                $usersTable,
                'master_user',
                'BINARY ' . $masterUuid . ' = BINARY ' . $masterId
            )
            ->leftJoin(
                'audit',
                $usersTable,
                'slave_user',
                'BINARY ' . $slaveUuid . ' = BINARY ' . $slaveId
            );

        if (isset($searchParams['search']) && is_scalar($searchParams['search'])) {
            $search = trim((string)$searchParams['search']);

            if ($search !== '') {
                $searchColumns = [
                    $auditId,
                    $masterId,
                    $slaveId,
                    $masterUsername,
                    $slaveUsername,
                    $masterFirstname,
                    $slaveFirstname,
                    $masterLastname,
                    $slaveLastname
                ];
                $conditions = array_map(
                    static fn (string $column): string => $column . ' LIKE :search',
                    $searchColumns
                );

                $QueryBuilder
                    ->andWhere('(' . $QueryBuilder->expr()->or(...$conditions) . ')')
                    ->setParameter('search', '%' . $search . '%');
            }
        }

        if (!empty($searchParams['count'])) {
            $count = $QueryBuilder
                ->select('COUNT(' . $auditId . ')')
                ->executeQuery()
                ->fetchOne();

            return (int)$count;
        }

        $sortColumns = [
            'id' => $auditId,
            'master' => $masterUsername,
            'slave' => $slaveUsername,
            'start' => $start,
            'end' => $end,
            'status' => $end
        ];
        $sortOn = isset($searchParams['sortOn']) && is_scalar($searchParams['sortOn'])
            ? (string)$searchParams['sortOn']
            : 'start';
        $sortBy = isset($searchParams['sortBy']) && is_scalar($searchParams['sortBy'])
            ? (string)$searchParams['sortBy']
            : 'DESC';
        $sortBy = in_array($sortBy, ['ASC', 'DESC'], true) ? $sortBy : 'DESC';

        if (!isset($sortColumns[$sortOn])) {
            $sortOn = 'start';
        }

        $QueryBuilder
            ->select(
                $auditId . ' AS ' . $Platform->quoteSingleIdentifier('id'),
                $masterId . ' AS ' . $Platform->quoteSingleIdentifier('master_id'),
                $slaveId . ' AS ' . $Platform->quoteSingleIdentifier('slave_id'),
                $start . ' AS ' . $Platform->quoteSingleIdentifier('start'),
                $end . ' AS ' . $Platform->quoteSingleIdentifier('end'),
                $masterUsername . ' AS ' . $Platform->quoteSingleIdentifier('master_username'),
                $masterFirstname . ' AS ' . $Platform->quoteSingleIdentifier('master_firstname'),
                $masterLastname . ' AS ' . $Platform->quoteSingleIdentifier('master_lastname'),
                $slaveUsername . ' AS ' . $Platform->quoteSingleIdentifier('slave_username'),
                $slaveFirstname . ' AS ' . $Platform->quoteSingleIdentifier('slave_firstname'),
                $slaveLastname . ' AS ' . $Platform->quoteSingleIdentifier('slave_lastname')
            )
            ->orderBy($sortColumns[$sortOn], $sortBy);

        $Grid = new Grid($searchParams);
        $gridParams = $Grid->parseDBParams($searchParams);

        if (isset($gridParams['limit'])) {
            $limit = explode(',', (string)$gridParams['limit'], 2);

            if (isset($limit[1])) {
                $QueryBuilder
                    ->setFirstResult((int)$limit[0])
                    ->setMaxResults((int)$limit[1]);
            }
        }

        $rows = $QueryBuilder->executeQuery()->fetchAllAssociative();

        return array_map(static function (array $row): array {
            $row['master'] = self::formatUserLabel($row, 'master');
            $row['slave'] = self::formatUserLabel($row, 'slave');
            $row['open'] = empty($row['end']);
            $row['duration'] = self::formatDuration((string)$row['start'], $row['end'] ?? null);

            unset(
                $row['master_username'],
                $row['master_firstname'],
                $row['master_lastname'],
                $row['slave_username'],
                $row['slave_firstname'],
                $row['slave_lastname']
            );

            return $row;
        }, $rows);
    }

    /**
     * @param array<string, mixed> $row
     */
    private static function formatUserLabel(array $row, string $prefix): string
    {
        $username = trim((string)($row[$prefix . '_username'] ?? ''));
        $name = trim(
            (string)($row[$prefix . '_firstname'] ?? '') . ' '
            . (string)($row[$prefix . '_lastname'] ?? '')
        );

        if ($name !== '' && $username !== '') {
            return $name . ' (' . $username . ')';
        }

        if ($name !== '') {
            return $name;
        }

        if ($username !== '') {
            return $username;
        }

        return (string)($row[$prefix . '_id'] ?? '');
    }

    private static function formatDuration(string $start, mixed $end): string
    {
        try {
            $Start = new DateTimeImmutable($start);
            $End = empty($end) ? new DateTimeImmutable() : new DateTimeImmutable((string)$end);
            $seconds = $End->getTimestamp() - $Start->getTimestamp();

            if ($seconds < 0) {
                $seconds = 0;
            }

            $days = intdiv($seconds, 86400);
            $hours = intdiv($seconds % 86400, 3600);
            $minutes = intdiv($seconds % 3600, 60);
            $remainingSeconds = $seconds % 60;

            if ($days > 0) {
                return sprintf('%dd %02d:%02d:%02d', $days, $hours, $minutes, $remainingSeconds);
            }

            return sprintf('%02d:%02d:%02d', $hours, $minutes, $remainingSeconds);
        } catch (\Throwable) {
            return '';
        }
    }
}
