<?php
/*
 * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the LICENSE file in the project root for the full license text.
 */

declare(strict_types=1);

namespace App\Controllers;

use App\Auth\SessionGuard;
use App\Http\Request;
use App\Http\Response;
use App\Http\View;
use App\Repositories\AuditRepository;
use App\Repositories\UserRepository;
use DateTimeImmutable;

/**
 * /audit — admin-only read-only viewer.
 *
 * Never writes to the audit log (that goes through AuditLogger), and there
 * is NO admin action here that truncates or deletes rows. The spec forbids
 * that.
 */
final class AuditController
{
    private const PAGE_SIZE = 50;

    public function __construct(
        private readonly UserRepository   $users,
        private readonly AuditRepository  $audit,
        private readonly View             $view,
    ) {
    }

    public function index(Request $req): Response
    {
        $actor = SessionGuard::requireAdmin($this->users);
        if ($actor instanceof Response) {
            return $actor;
        }

        $rawFrom = $req->queryString('from_date');
        $rawTo   = $req->queryString('to_date');
        $dates   = self::validateDateFilters($rawFrom, $rawTo);

        // Form inputs echo what the user typed (so they can fix a typo);
        // the repo only sees the values that parsed cleanly as Y-m-d.
        $filters = [
            'user_email'  => $req->queryString('user_email'),
            'action'      => $req->queryString('action'),
            'entity_type' => $req->queryString('entity_type'),
            'entity_id'   => $req->queryString('entity_id'),
            'from_date'   => $rawFrom,
            'to_date'     => $rawTo,
        ];

        $effectiveFilters = $filters;
        $effectiveFilters['from_date'] = $dates['from'];
        $effectiveFilters['to_date']   = $dates['to'];

        $page   = (int) ($req->queryString('page') ?: '1');
        $result = $this->audit->findPaged($effectiveFilters, $page, self::PAGE_SIZE);

        return Response::html($this->view->render('audit/index', [
            'title'       => 'Audit log',
            'currentUser' => $actor,
            'csrfToken'   => SessionGuard::csrfToken(),
            'filters'     => $filters,
            'dateErrors'  => $dates['errors'],
            'page'        => $result['page'],
            'pages'       => $result['pages'],
            'pageSize'    => $result['pageSize'],
            'total'       => $result['total'],
            'rows'        => $result['rows'],
            'actions'     => $this->audit->distinctActions(),
            'entityTypes' => $this->audit->distinctEntityTypes(),
            'users'       => $this->audit->distinctUserEmails(),
        ]));
    }

    /**
     * Pure helper: parse the raw `from_date` / `to_date` query inputs,
     * drop anything that is not a strict `Y-m-d`, and report which fields
     * the user got wrong. The repo concatenates `T00:00:00Z` / `T23:59:59Z`
     * onto whatever it gets, so any garbage that reaches it just sorts
     * lexically against `occurred_at` and silently hides rows (R01-N12).
     *
     * @return array{from: string, to: string, errors: array<string,string>}
     */
    public static function validateDateFilters(string $from, string $to): array
    {
        $errors = [];

        $effFrom = '';
        if ($from !== '') {
            if (self::isIsoDate($from)) {
                $effFrom = $from;
            } else {
                $errors['from_date'] = 'Use the format YYYY-MM-DD.';
            }
        }

        $effTo = '';
        if ($to !== '') {
            if (self::isIsoDate($to)) {
                $effTo = $to;
            } else {
                $errors['to_date'] = 'Use the format YYYY-MM-DD.';
            }
        }

        return ['from' => $effFrom, 'to' => $effTo, 'errors' => $errors];
    }

    private static function isIsoDate(string $s): bool
    {
        $d = DateTimeImmutable::createFromFormat('Y-m-d', $s);
        return $d !== false && $d->format('Y-m-d') === $s;
    }
}
