<?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\UserRepository;
use App\Repositories\WorkerRepository;
use App\Services\AuditLogger;
use PDO;
use PDOException;
use Throwable;

/**
 * /workers — master data for the people to whom sprint tasks are assigned.
 *
 * Access: admin only (all methods). Workers are independent from users: a
 * worker is not required to ever log in.
 */
final class WorkerController
{
    public function __construct(
        private readonly PDO               $pdo,
        private readonly UserRepository    $users,
        private readonly WorkerRepository  $workers,
        private readonly AuditLogger       $audit,
        private readonly View              $view,
    ) {
    }

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

        return Response::html($this->view->render('workers/index', [
            'title'       => 'Workers',
            'currentUser' => $actor,
            'csrfToken'   => SessionGuard::csrfToken(),
            'workers'     => $this->workers->all(),
            'flash'       => $req->queryString('flash'),
            'error'       => $req->queryString('error'),
        ]));
    }

    /** POST /workers — create a worker. */
    public function create(Request $req): Response
    {
        $actor = SessionGuard::requireAdminForm($req, $this->users);
        if ($actor instanceof Response) {
            return $actor;
        }

        $name    = trim($req->postString('name'));
        $rtbRaw  = $req->postString('default_rtb');
        $rtb     = $rtbRaw === '' ? 0.0 : (float) $rtbRaw;

        if ($name === '') {
            return Response::redirect('/workers?error=name_required');
        }
        if ($rtb < 0.0 || $rtb > 1.0) {
            return Response::redirect('/workers?error=rtb_out_of_range');
        }

        $this->pdo->beginTransaction();
        try {
            $worker = $this->workers->create($name, true, $rtb);
            $this->audit->recordForRequest(
                action:     'CREATE',
                entityType: 'worker',
                entityId:   $worker->id,
                before:     null,
                after:      $worker->toAuditSnapshot(),
                req:        $req,
                actor:      $actor,
            );
            $this->pdo->commit();
        } catch (PDOException $e) {
            $this->pdo->rollBack();
            // UNIQUE(name) violation or similar.
            return Response::redirect('/workers?error=' . urlencode(
                str_contains(strtolower($e->getMessage()), 'unique') ? 'name_taken' : 'db_error'
            ));
        } catch (Throwable) {
            $this->pdo->rollBack();
            return Response::redirect('/workers?error=db_error');
        }

        return Response::redirect('/workers?flash=created');
    }

    /** POST /workers/{id} (form) — update a worker's editable fields. */
    public function update(Request $req, array $params): Response
    {
        $actor = SessionGuard::requireAdminForm($req, $this->users);
        if ($actor instanceof Response) {
            return $actor;
        }

        $id = (int) $params['id'];
        $existing = $this->workers->find($id);
        if ($existing === null) {
            return Response::text('Not Found', 404);
        }

        $changes = [];

        $name = trim($req->postString('name'));
        if ($name !== '' && $name !== $existing->name) {
            $changes['name'] = $name;
        }

        $rtbRaw = $req->postString('default_rtb');
        if ($rtbRaw !== '') {
            $rtb = (float) $rtbRaw;
            if ($rtb < 0.0 || $rtb > 1.0) {
                return Response::redirect('/workers?error=rtb_out_of_range');
            }
            if (abs($rtb - $existing->defaultRtb) > 1e-9) {
                $changes['default_rtb'] = $rtb;
            }
        }

        // is_active comes via checkbox — absent means "off".
        $isActive = isset($req->post['is_active'])
            && (string) $req->post['is_active'] !== '0';
        if ($isActive !== $existing->isActive) {
            $changes['is_active'] = $isActive;
        }

        if ($changes === []) {
            return Response::redirect('/workers?flash=noop');
        }

        $this->pdo->beginTransaction();
        try {
            $result = $this->workers->update($id, $changes);
            $this->audit->recordForRequest(
                action:     'UPDATE',
                entityType: 'worker',
                entityId:   $id,
                before:     $result['before']->toAuditSnapshot(),
                after:      $result['after']->toAuditSnapshot(),
                req:        $req,
                actor:      $actor,
            );
            $this->pdo->commit();
        } catch (PDOException $e) {
            $this->pdo->rollBack();
            return Response::redirect('/workers?error=' . urlencode(
                str_contains(strtolower($e->getMessage()), 'unique') ? 'name_taken' : 'db_error'
            ));
        } catch (Throwable) {
            $this->pdo->rollBack();
            return Response::redirect('/workers?error=db_error');
        }

        return Response::redirect('/workers?flash=updated');
    }
}
