<?php

declare(strict_types=1);

require_once __DIR__ . '/DBConnection.php';

/** Two-purpose ChronoForms action for the vote-release flag. */
final class MetadataVoteReleaseAction
{
    public const BUILD_ID = 'METADATA-VOTE-RELEASE-KISS-20261018-02';

    private const TABLE = 'nvbjn_chronoforms_campaign_metadata';
    private const FLASH_KEY = 'wibe.metadata.vote_release.flash.';

    /** Render a pending result in the shared process-information area. */
    public static function prepareDetail($cf): void
    {
        if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) !== 'GET') {
            return;
        }
        $app = Joomla\CMS\Factory::getApplication();
        $campaignId = max(0, $app->input->getInt('campaignid', 0));
        if (is_object($cf) && method_exists($cf, 'set')) {
            $cf->set('wibe_vote_release_campaign_id', $campaignId);
            $cf->set('wibe_vote_release_token', Joomla\CMS\Session\Session::getFormToken());
            if (method_exists($cf, 'data')) {
                $cf->data('wibe_vote_release_campaign_id', $campaignId, true);
                $cf->data('wibe_vote_release_token', Joomla\CMS\Session\Session::getFormToken(), true);
            }
        }
        $html = self::consumeFlash($campaignId);
        if ($html === '') {
            return;
        }
        require_once __DIR__ . '/MetadataProcessInformation.php';
        echo MetadataProcessInformation::wrap('vote-release', $html);
    }

    /** Dedicated POST entry point for form.vote-release. */
    public static function handleReleasePost($cf): void
    {
        self::handlePost('release');
    }

    /** Dedicated POST entry point for form.vote-withdraw. */
    public static function handleWithdrawPost($cf): void
    {
        self::handlePost('withdraw');
    }

    /** Set vote_release from 0 to 1, or report that it is already set. */
    public static function releaseVote(int $campaignId): array
    {
        self::assertPostSecurity();
        return self::changeVoteRelease($campaignId, 0, 1);
    }

    /** Set vote_release from 1 to 0, or report that it is already cleared. */
    public static function withdrawVote(int $campaignId): array
    {
        self::assertPostSecurity();
        $result = self::changeVoteRelease($campaignId, 1, 0);
        if (!empty($result['changed'])) {
            // The transactional readback proves only the pending transaction.
            // Verify the committed withdrawal over an independent connection
            // before handlePost() is allowed to publish a success message.
            $committed = self::load(self::connection(), $campaignId, false);
            if ((int) $committed['campaignstatus'] !== 5
                || (int) $committed['vote_release'] !== 0) {
                throw new RuntimeException('vote_release_post_commit_readback_failed');
            }
        }
        return $result;
    }

    private static function handlePost(string $action): void
    {
        $app = Joomla\CMS\Factory::getApplication();
        $campaignId = max(
            0,
            $app->input->post->getInt('campaignid', 0),
            $app->input->getInt('campaignid', 0)
        );
        try {
            $result = $action === 'release'
                ? self::releaseVote($campaignId)
                : self::withdrawVote($campaignId);
            self::storeFlash($campaignId, [
                'type' => 'success',
                'message' => self::successMessage($action, !empty($result['changed'])),
            ]);
        } catch (Throwable $error) {
            self::storeFlash($campaignId, [
                'type' => 'error',
                'message' => self::publicError(self::safeReason($error)),
            ]);
        }
        self::redirectToDetail($campaignId);
    }

    /** @return array{changed:bool,vote_release:int} */
    private static function changeVoteRelease(int $campaignId, int $expected, int $target): array
    {
        if ($campaignId < 1) {
            throw new RuntimeException('vote_release_campaign_context_invalid');
        }
        $pdo = self::connection();
        if (!$pdo->beginTransaction()) {
            throw new RuntimeException('vote_release_transaction_failed');
        }
        try {
            $row = self::load($pdo, $campaignId, true);
            if ((int) $row['campaignstatus'] !== 5) {
                throw new RuntimeException('vote_release_status_not_ready');
            }
            $current = (int) $row['vote_release'];
            if ($current === $target) {
                $pdo->rollBack();
                return ['changed' => false, 'vote_release' => $current];
            }
            if ($current !== $expected) {
                throw new RuntimeException('vote_release_state_invalid');
            }

            $statement = $pdo->prepare(
                'UPDATE `' . self::TABLE . '` SET `vote_release`=:target '
                . 'WHERE `id`=:campaign_id AND `vote_release`=:expected AND `campaignstatus`=5'
            );
            $statement->execute([
                'target' => $target,
                'campaign_id' => $campaignId,
                'expected' => $expected,
            ]);
            if ($statement->rowCount() !== 1) {
                throw new RuntimeException('vote_release_concurrent_change');
            }
            $readback = self::load($pdo, $campaignId, false);
            if ((int) $readback['campaignstatus'] !== 5
                || (int) $readback['vote_release'] !== $target) {
                throw new RuntimeException('vote_release_readback_failed');
            }
            if (!$pdo->commit()) {
                throw new RuntimeException('vote_release_commit_failed');
            }

            return ['changed' => true, 'vote_release' => $target];
        } catch (Throwable $error) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $error;
        }
    }

    /** @return array{id:int,campaignstatus:int,vote_release:int} */
    private static function load(PDO $pdo, int $campaignId, bool $forUpdate): array
    {
        $statement = $pdo->prepare(
            'SELECT `id`,`campaignstatus`,`vote_release` FROM `' . self::TABLE . '` '
            . 'WHERE `id`=:campaign_id LIMIT 1' . ($forUpdate ? ' FOR UPDATE' : '')
        );
        $statement->execute(['campaign_id' => $campaignId]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);
        if (!is_array($row)) {
            throw new RuntimeException('vote_release_campaign_not_found');
        }
        return $row;
    }

    private static function assertPostSecurity(): void
    {
        $app = Joomla\CMS\Factory::getApplication();
        if (strtoupper((string) $app->input->getMethod()) !== 'POST') {
            throw new RuntimeException('vote_release_post_required');
        }
        if (!self::authorized()) {
            throw new RuntimeException('vote_release_permission_required');
        }
        $submittedToken = $app->input->post->getCmd('wibe_vote_release_token', '');
        $expectedToken = Joomla\CMS\Session\Session::getFormToken();
        if ($submittedToken === '' || !hash_equals($expectedToken, $submittedToken)) {
            throw new RuntimeException('vote_release_csrf_invalid');
        }
    }

    private static function authorized(): bool
    {
        $identity = Joomla\CMS\Factory::getApplication()->getIdentity();
        return $identity
            && !empty($identity->id)
            && empty($identity->guest)
            && ((bool) $identity->authorise('core.edit', 'com_chronoforms7')
                || (bool) $identity->authorise('core.manage', 'com_chronoforms7'));
    }

    private static function connection(): PDO
    {
        require __DIR__ . '/setting.config.php';
        if (empty($config) || !is_array($config)) {
            throw new RuntimeException('database_config_missing');
        }
        return (new DBConnection($config))->dbc;
    }

    private static function successMessage(string $action, bool $changed): string
    {
        if ($action === 'release') {
            return $changed
                ? 'Die Volksinitiative wurde für die Abstimmung freigeschaltet.'
                : 'Volksinitiative für Abstimmung bereits freigeschaltet.';
        }
        return $changed
            ? 'Die Abstimmungsfreigabe der Volksinitiative wurde zurückgenommen.'
            : 'Volksinitiative ist für Abstimmung nicht freigeschaltet. Für eine Freischaltung bitte &quot;Abstimmung freigeben&quot; aktivieren.';
    }

    /** @param array{type:string,message:string} $message */
    private static function storeFlash(int $campaignId, array $message): void
    {
        if ($campaignId < 1) {
            return;
        }
        $app = Joomla\CMS\Factory::getApplication();
        $identity = $app->getIdentity();
        $message['campaign_id'] = $campaignId;
        $message['user_id'] = (int) ($identity->id ?? 0);
        $app->getSession()->set(self::flashKey($campaignId), $message);
    }

    private static function consumeFlash(int $campaignId): string
    {
        if ($campaignId < 1) {
            return '';
        }
        $app = Joomla\CMS\Factory::getApplication();
        $identity = $app->getIdentity();
        $key = self::flashKey($campaignId);
        $message = (array) $app->getSession()->get($key, []);
        $app->getSession()->clear($key);
        if ($message === []
            || (int) ($message['campaign_id'] ?? 0) !== $campaignId
            || (int) ($message['user_id'] ?? 0) !== (int) ($identity->id ?? 0)) {
            return '';
        }
        $type = ($message['type'] ?? '') === 'success' ? 'positive' : 'negative';
        return '<div class="ui ' . $type . ' message" role="status"><p>'
            . (string) ($message['message'] ?? '') . '</p></div>';
    }

    private static function flashKey(int $campaignId): string
    {
        $identity = Joomla\CMS\Factory::getApplication()->getIdentity();
        return self::FLASH_KEY . (int) ($identity->id ?? 0) . '.' . $campaignId;
    }

    private static function redirectToDetail(int $campaignId): void
    {
        $app = Joomla\CMS\Factory::getApplication();
        $url = Joomla\CMS\Router\Route::_(
            'index.php?option=com_chronoforms7&chronoform=metadata&gpage=detail'
            . ($campaignId > 0 ? '&campaignid=' . $campaignId : ''),
            false
        );
        $app->redirect($url, 303);
        $app->close();
    }

    private static function safeReason(Throwable $error): string
    {
        $message = trim($error->getMessage());
        return preg_match('/^[a-z][a-z0-9_.-]{0,119}$/i', $message) === 1
            ? $message
            : 'runtime_error';
    }

    private static function publicError(string $code): string
    {
        return match ($code) {
            'vote_release_post_required' => 'Die Abstimmungsfreigabe kann nur über den vorgesehenen Button geändert werden.',
            'vote_release_permission_required' => 'Für diese Aktion fehlt die erforderliche Administratorberechtigung.',
            'vote_release_csrf_invalid' => 'Die Sicherheitsprüfung ist fehlgeschlagen. Bitte laden Sie die Seite neu.',
            'vote_release_campaign_context_invalid',
            'vote_release_campaign_not_found' => 'Die Kampagne konnte nicht eindeutig bestimmt werden.',
            'vote_release_status_not_ready' => 'Die Kampagne ist nicht im Status «Abstimmungsreif».',
            'vote_release_state_invalid',
            'vote_release_concurrent_change' => 'Der Freigabestatus wurde zwischenzeitlich geändert. Bitte laden Sie die Seite neu.',
            default => 'Die Abstimmungsfreigabe konnte nicht geändert werden.',
        };
    }
}
