<?php
declare(strict_types=1);

namespace App\Modules\Settings;

use App\Core\Database\Migrator;
use App\Core\Http\Request;
use App\Core\Http\Response;
use App\Core\I18n\Translator;
use App\Core\Security\Csrf;
use App\Core\Support\Flash;
use App\Core\Support\StringHelper;
use App\Core\View\Template;
use App\Modules\Auth\AuthService;
use Throwable;

final class SettingsController
{
    public function __construct(
        private readonly Template $template,
        private readonly Translator $translator,
        private readonly AuthService $auth,
        private readonly Migrator $migrator,
        private readonly OrderStatusRepository $orderStatuses
    ) {
    }

    public function database(Request $request): Response
    {
        $html = $this->template->render('settings/database', [
            'title' => $this->translator->get('settings.database.title'),
            'activeMenu' => 'settings',
            'activeSettings' => 'database',
            'user' => $this->auth->user(),
            'csrfToken' => Csrf::token(),
            'status' => $this->migrator->status(),
            'errorMessage' => (string) Flash::get('settings_error', ''),
            'successMessage' => (string) Flash::get('settings_success', ''),
            'runLogs' => (array) Flash::get('settings_migrate_logs', []),
        ], 'layouts/app');

        return Response::html($html);
    }

    public function migrate(Request $request): Response
    {
        $csrfToken = (string) $request->input('_token', '');
        if (!Csrf::validate($csrfToken)) {
            Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired'));
            return Response::redirect('/settings/database');
        }

        try {
            $result = $this->migrator->runPending();
            $executed = (int) ($result['executed'] ?? 0);
            $skipped = (int) ($result['skipped'] ?? 0);

            Flash::set(
                'settings_success',
                $this->translator->get('settings.database.flash.updated', [
                    'executed' => (string) $executed,
                    'skipped' => (string) $skipped,
                ])
            );
            Flash::set('settings_migrate_logs', (array) ($result['logs'] ?? []));
        } catch (Throwable $exception) {
            Flash::set(
                'settings_error',
                $this->translator->get('settings.database.flash.failed') . ' ' . $exception->getMessage()
            );
            Flash::set('settings_migrate_logs', ['[error] ' . $exception->getMessage()]);
        }

        return Response::redirect('/settings/database');
    }

    public function statuses(Request $request): Response
    {
        $html = $this->template->render('settings/statuses', [
            'title' => $this->translator->get('settings.statuses.title'),
            'activeMenu' => 'settings',
            'activeSettings' => 'statuses',
            'user' => $this->auth->user(),
            'csrfToken' => Csrf::token(),
            'groups' => $this->orderStatuses->listGroups(),
            'statuses' => $this->orderStatuses->listStatuses(),
            'errorMessage' => (string) Flash::get('settings_error', ''),
            'successMessage' => (string) Flash::get('settings_success', ''),
        ], 'layouts/app');

        return Response::html($html);
    }

    public function createStatusGroup(Request $request): Response
    {
        $csrfErrorResponse = $this->validateCsrf((string) $request->input('_token', ''));
        if ($csrfErrorResponse !== null) {
            return $csrfErrorResponse;
        }

        $name = trim((string) $request->input('name', ''));
        if (mb_strlen($name) < 2) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_name_invalid'));
            return Response::redirect('/settings/statuses');
        }

        $code = $this->normalizeCode((string) $request->input('code', ''), $name);
        if ($code === '') {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_code_invalid'));
            return Response::redirect('/settings/statuses');
        }
        if ($this->orderStatuses->existsGroupCode($code)) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_code_taken'));
            return Response::redirect('/settings/statuses');
        }

        $colorHex = StringHelper::normalizeColorHex((string) $request->input('color_hex', '#64748b'));
        $isActive = (string) $request->input('is_active', '1') === '1';

        try {
            $sortOrder = $this->orderStatuses->nextGroupSortOrder();
            $this->orderStatuses->createGroup($name, $code, $colorHex, $sortOrder, $isActive);
            Flash::set('settings_success', $this->translator->get('settings.statuses.flash.group_created'));
        } catch (Throwable $exception) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_create_failed') . ' ' . $exception->getMessage());
        }

        return Response::redirect('/settings/statuses');
    }

    public function updateStatusGroup(Request $request): Response
    {
        $csrfErrorResponse = $this->validateCsrf((string) $request->input('_token', ''));
        if ($csrfErrorResponse !== null) {
            return $csrfErrorResponse;
        }

        $groupId = max(0, (int) $request->input('group_id', 0));
        $existingGroup = $groupId > 0 ? $this->orderStatuses->findGroupById($groupId) : null;
        if ($existingGroup === null) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_not_found'));
            return Response::redirect('/settings/statuses');
        }

        $name = trim((string) $request->input('name', ''));
        if (mb_strlen($name) < 2) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_name_invalid'));
            return Response::redirect('/settings/statuses');
        }

        $colorHex = StringHelper::normalizeColorHex((string) $request->input('color_hex', '#64748b'));
        $isActive = (string) $request->input('is_active', '0') === '1';
        $code = trim((string) ($existingGroup['code'] ?? ''));

        try {
            $this->orderStatuses->updateGroup($groupId, $name, $code, $colorHex, $isActive);
            Flash::set('settings_success', $this->translator->get('settings.statuses.flash.group_updated'));
        } catch (Throwable $exception) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_update_failed') . ' ' . $exception->getMessage());
        }

        return Response::redirect('/settings/statuses');
    }

    public function deleteStatusGroup(Request $request): Response
    {
        $csrfErrorResponse = $this->validateCsrf((string) $request->input('_token', ''));
        if ($csrfErrorResponse !== null) {
            return $csrfErrorResponse;
        }

        $groupId = max(0, (int) $request->input('group_id', 0));
        if ($groupId <= 0 || !$this->orderStatuses->groupExists($groupId)) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_not_found'));
            return Response::redirect('/settings/statuses');
        }

        try {
            $this->orderStatuses->deleteGroup($groupId);
            Flash::set('settings_success', $this->translator->get('settings.statuses.flash.group_deleted'));
        } catch (Throwable $exception) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_delete_failed') . ' ' . $exception->getMessage());
        }

        return Response::redirect('/settings/statuses');
    }

    public function createStatus(Request $request): Response
    {
        $csrfErrorResponse = $this->validateCsrf((string) $request->input('_token', ''));
        if ($csrfErrorResponse !== null) {
            return $csrfErrorResponse;
        }

        $groupId = max(0, (int) $request->input('group_id', 0));
        if ($groupId <= 0 || !$this->orderStatuses->groupExists($groupId)) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_required'));
            return Response::redirect('/settings/statuses');
        }

        $name = trim((string) $request->input('name', ''));
        if (mb_strlen($name) < 2) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.status_name_invalid'));
            return Response::redirect('/settings/statuses');
        }

        $code = $this->normalizeCode((string) $request->input('code', ''), $name);
        if ($code === '') {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.status_code_invalid'));
            return Response::redirect('/settings/statuses');
        }
        if ($this->orderStatuses->existsStatusCode($code)) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.status_code_taken'));
            return Response::redirect('/settings/statuses');
        }

        $isActive = (string) $request->input('is_active', '1') === '1';

        try {
            $sortOrder = $this->orderStatuses->nextStatusSortOrder($groupId);
            $this->orderStatuses->createStatus($groupId, $name, $code, $sortOrder, $isActive);
            Flash::set('settings_success', $this->translator->get('settings.statuses.flash.status_created'));
        } catch (Throwable $exception) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.status_create_failed') . ' ' . $exception->getMessage());
        }

        return Response::redirect('/settings/statuses');
    }

    public function updateStatus(Request $request): Response
    {
        $csrfErrorResponse = $this->validateCsrf((string) $request->input('_token', ''));
        if ($csrfErrorResponse !== null) {
            return $csrfErrorResponse;
        }

        $statusId = max(0, (int) $request->input('status_id', 0));
        $existingStatus = $statusId > 0 ? $this->orderStatuses->findStatusById($statusId) : null;
        if ($existingStatus === null) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.status_not_found'));
            return Response::redirect('/settings/statuses');
        }

        $groupId = max(0, (int) $request->input('group_id', 0));
        if ($groupId <= 0 || !$this->orderStatuses->groupExists($groupId)) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_required'));
            return Response::redirect('/settings/statuses');
        }

        $name = trim((string) $request->input('name', ''));
        if (mb_strlen($name) < 2) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.status_name_invalid'));
            return Response::redirect('/settings/statuses');
        }

        $isActive = (string) $request->input('is_active', '0') === '1';
        $code = trim((string) ($existingStatus['code'] ?? ''));

        try {
            $this->orderStatuses->updateStatus($statusId, $groupId, $name, $code, $isActive);
            Flash::set('settings_success', $this->translator->get('settings.statuses.flash.status_updated'));
        } catch (Throwable $exception) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.status_update_failed') . ' ' . $exception->getMessage());
        }

        return Response::redirect('/settings/statuses');
    }

    public function deleteStatus(Request $request): Response
    {
        $csrfErrorResponse = $this->validateCsrf((string) $request->input('_token', ''));
        if ($csrfErrorResponse !== null) {
            return $csrfErrorResponse;
        }

        $statusId = max(0, (int) $request->input('status_id', 0));
        if ($statusId <= 0 || !$this->orderStatuses->statusExists($statusId)) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.status_not_found'));
            return Response::redirect('/settings/statuses');
        }

        try {
            $this->orderStatuses->deleteStatus($statusId);
            Flash::set('settings_success', $this->translator->get('settings.statuses.flash.status_deleted'));
        } catch (Throwable $exception) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.status_delete_failed') . ' ' . $exception->getMessage());
        }

        return Response::redirect('/settings/statuses');
    }

    public function reorderStatusGroups(Request $request): Response
    {
        $csrfErrorResponse = $this->validateCsrf((string) $request->input('_token', ''));
        if ($csrfErrorResponse !== null) {
            return $csrfErrorResponse;
        }

        $rawOrder = $request->input('order', []);
        $orderedIds = $this->normalizeOrderedIds($rawOrder);
        if ($orderedIds === []) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.reorder_empty'));
            return Response::redirect('/settings/statuses');
        }

        try {
            $this->orderStatuses->reorderGroups($orderedIds);
            Flash::set('settings_success', $this->translator->get('settings.statuses.flash.group_reordered'));
        } catch (Throwable $exception) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_reorder_failed') . ' ' . $exception->getMessage());
        }

        return Response::redirect('/settings/statuses');
    }

    public function reorderStatuses(Request $request): Response
    {
        $csrfErrorResponse = $this->validateCsrf((string) $request->input('_token', ''));
        if ($csrfErrorResponse !== null) {
            return $csrfErrorResponse;
        }

        $groupId = max(0, (int) $request->input('group_id', 0));
        if ($groupId <= 0 || !$this->orderStatuses->groupExists($groupId)) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.group_required'));
            return Response::redirect('/settings/statuses');
        }

        $rawOrder = $request->input('order', []);
        $orderedIds = $this->normalizeOrderedIds($rawOrder);
        if ($orderedIds === []) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.reorder_empty'));
            return Response::redirect('/settings/statuses');
        }

        try {
            $this->orderStatuses->reorderStatusesByGroup($groupId, $orderedIds);
            Flash::set('settings_success', $this->translator->get('settings.statuses.flash.status_reordered'));
        } catch (Throwable $exception) {
            Flash::set('settings_error', $this->translator->get('settings.statuses.flash.status_reorder_failed') . ' ' . $exception->getMessage());
        }

        return Response::redirect('/settings/statuses');
    }

    private function validateCsrf(string $token): ?Response
    {
        if (Csrf::validate($token)) {
            return null;
        }

        Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired'));
        return Response::redirect('/settings/statuses');
    }

    private function normalizeCode(string $code, string $fallbackName = ''): string
    {
        $source = trim($code) !== '' ? $code : $fallbackName;
        $normalized = mb_strtolower(trim($source));
        if ($normalized === '') {
            return '';
        }

        $normalized = strtr($normalized, [
            'ą' => 'a',
            'ć' => 'c',
            'ę' => 'e',
            'ł' => 'l',
            'ń' => 'n',
            'ó' => 'o',
            'ś' => 's',
            'ź' => 'z',
            'ż' => 'z',
        ]);

        $normalized = preg_replace('/[^a-z0-9_\-\.]+/u', '_', $normalized) ?? '';
        $normalized = trim($normalized, '_');

        return mb_substr($normalized, 0, 64);
    }

    /**
     * @return array<int, int>
     */
    private function normalizeOrderedIds(mixed $rawOrder): array
    {
        if (!is_array($rawOrder)) {
            return [];
        }

        $result = [];
        foreach ($rawOrder as $idRaw) {
            $id = max(0, (int) $idRaw);
            if ($id <= 0) {
                continue;
            }
            if (in_array($id, $result, true)) {
                continue;
            }
            $result[] = $id;
        }

        return $result;
    }
}
