<?php

declare(strict_types=1);

namespace App\Modules\Support\Application;

use App\Modules\Support\Domain\SupportRequest;
use App\Modules\Support\Domain\SupportRequestMessage;
use PDO;
use RuntimeException;

class SupportService
{
    private const DIRECTIONS = [
        'member_to_tenant' => [
            'label' => 'Mitglied -> Tenant',
            'route_target' => 'tenant_responsible',
        ],
        'tenant_to_platform' => [
            'label' => 'Tenant -> Plattform',
            'route_target' => 'platform_admin',
        ],
    ];

    private const STATUSES = [
        'new',
        'open',
        'in_progress',
        'waiting_on_user',
        'resolved',
        'closed',
    ];

    private const PRIORITIES = [
        'low',
        'normal',
        'high',
        'urgent',
    ];

    private const CATEGORIES = [
        'general',
        'payment',
        'access',
        'content',
        'survey',
        'technical',
        'other',
    ];

    private const ROUTE_TARGETS = [
        'tenant_responsible',
        'platform_admin',
    ];

    /**
     * @return array<string, array<string, string>>
     */
    public function directions(): array
    {
        return self::DIRECTIONS;
    }

    /**
     * @return array<int, string>
     */
    public function statuses(): array
    {
        return self::STATUSES;
    }

    /**
     * @return array<int, string>
     */
    public function priorities(): array
    {
        return self::PRIORITIES;
    }

    /**
     * @return array<int, string>
     */
    public function categories(): array
    {
        return self::CATEGORIES;
    }

    /**
     * @return array<int, string>
     */
    public function routeTargets(): array
    {
        return self::ROUTE_TARGETS;
    }

    public function canManage(array $auth): bool
    {
        return $this->isPlatformAdmin($auth) || $this->hasRole($auth, 'tenant_admin') || $this->hasRole($auth, 'support_contact');
    }

    public function canReply(array $auth, SupportRequest $request): bool
    {
        if ($this->canManage($auth)) {
            return true;
        }

        return (string) ($auth['user_id'] ?? '') !== ''
            && (
                (string) ($request->requesterUserId ?? '') === (string) ($auth['user_id'] ?? '')
                || (string) ($request->requesterTenantUserId ?? '') === (string) ($auth['tenant_user_id'] ?? '')
            );
    }

    /**
     * @return array<int, SupportRequest>
     */
    public function listForTenant(PDO $pdo, array $auth): array
    {
        $tenantId = (string) ($auth['tenant_id'] ?? '');

        if ($tenantId === '') {
            return [];
        }

        if ($this->canManage($auth)) {
            $rows = $this->queryAll(
                $pdo,
                <<<'SQL'
SELECT *
FROM support_requests
WHERE tenant_id = :tenant_id
ORDER BY last_message_at DESC, created_at DESC
SQL,
                ['tenant_id' => $tenantId]
            );
        } else {
            $rows = $this->queryAll(
                $pdo,
                <<<'SQL'
SELECT *
FROM support_requests
WHERE tenant_id = :tenant_id
  AND (
        requester_user_id = :user_id
        OR requester_tenant_user_id = :tenant_user_id
      )
ORDER BY last_message_at DESC, created_at DESC
SQL,
                [
                    'tenant_id' => $tenantId,
                    'user_id' => (string) ($auth['user_id'] ?? ''),
                    'tenant_user_id' => (string) ($auth['tenant_user_id'] ?? ''),
                ]
            );
        }

        return array_map(fn(array $row): SupportRequest => $this->hydrateRequest($pdo, $row), $rows);
    }

    public function find(PDO $pdo, array $auth, string $requestId): ?SupportRequest
    {
        $tenantId = (string) ($auth['tenant_id'] ?? '');

        if ($tenantId === '' || trim($requestId) === '') {
            return null;
        }

        $row = $this->queryOne(
            $pdo,
            <<<'SQL'
SELECT *
FROM support_requests
WHERE tenant_id = :tenant_id
  AND id = :id
LIMIT 1
SQL,
            [
                'tenant_id' => $tenantId,
                'id' => $requestId,
            ]
        );

        if ($row === null) {
            return null;
        }

        $request = $this->hydrateRequest($pdo, $row);

        if (!$this->canManage($auth) && !$this->canReply($auth, $request)) {
            return null;
        }

        return $request;
    }

    /**
     * @return array<string, int>
     */
    public function summary(PDO $pdo, array $auth): array
    {
        $requests = $this->listForTenant($pdo, $auth);
        $summary = [
            'all' => count($requests),
            'new' => 0,
            'open' => 0,
            'waiting_on_user' => 0,
            'resolved' => 0,
            'closed' => 0,
        ];

        foreach ($requests as $request) {
            if (array_key_exists($request->status, $summary)) {
                $summary[$request->status]++;
            }
        }

        return $summary;
    }

    public function create(PDO $pdo, array $auth, array $input): SupportRequest
    {
        $tenantId = (string) ($auth['tenant_id'] ?? '');
        $subject = trim((string) ($input['subject'] ?? ''));
        $message = trim((string) ($input['message'] ?? ''));
        $category = trim((string) ($input['category'] ?? 'general'));
        $direction = trim((string) ($input['request_direction'] ?? 'member_to_tenant'));
        $priority = trim((string) ($input['priority'] ?? 'normal'));

        if ($tenantId === '') {
            throw new RuntimeException('Der Support benötigt einen aktiven Tenant.');
        }

        if ($subject === '') {
            throw new RuntimeException('Bitte gib einen Betreff an.');
        }

        if ($message === '') {
            throw new RuntimeException('Bitte beschreibe dein Anliegen.');
        }

        if (!in_array($category, self::CATEGORIES, true)) {
            $category = 'general';
        }

        if (!array_key_exists($direction, self::DIRECTIONS)) {
            $direction = 'member_to_tenant';
        }

        if (!in_array($priority, self::PRIORITIES, true)) {
            $priority = 'normal';
        }

        $routeTarget = self::DIRECTIONS[$direction]['route_target'];
        $requestNumber = 'SR-' . date('YmdHis') . '-' . strtoupper(substr(app_uuid(), 0, 8));
        $now = date('Y-m-d H:i:s');
        $requestId = app_uuid();
        $authorName = trim((string) ($auth['display_name'] ?? $auth['email'] ?? ''));
        $authorEmail = strtolower(trim((string) ($auth['email'] ?? '')));
        $authorUserId = (string) ($auth['user_id'] ?? '');
        $authorTenantUserId = (string) ($auth['tenant_user_id'] ?? '');

        if ($authorName === '') {
            $authorName = 'Unbekannt';
        }

        $pdo->beginTransaction();

        try {
            $this->execute(
                $pdo,
                <<<'SQL'
INSERT INTO support_requests (
    id, tenant_id, request_number, request_direction, route_target,
    category, priority, status, subject, message,
    requester_user_id, requester_tenant_user_id, requester_name, requester_email,
    assigned_to_user_id, assigned_to_tenant_user_id, last_message_at, resolved_at, closed_at,
    created_at, updated_at
) VALUES (
    :id, :tenant_id, :request_number, :request_direction, :route_target,
    :category, :priority, :status, :subject, :message,
    :requester_user_id, :requester_tenant_user_id, :requester_name, :requester_email,
    NULL, NULL, :last_message_at, NULL, NULL,
    :created_at, :updated_at
)
SQL,
                [
                    'id' => $requestId,
                    'tenant_id' => $tenantId,
                    'request_number' => $requestNumber,
                    'request_direction' => $direction,
                    'route_target' => $routeTarget,
                    'category' => $category,
                    'priority' => $priority,
                    'status' => 'new',
                    'subject' => $subject,
                    'message' => $message,
                    'requester_user_id' => $authorUserId !== '' ? $authorUserId : null,
                    'requester_tenant_user_id' => $authorTenantUserId !== '' ? $authorTenantUserId : null,
                    'requester_name' => $authorName,
                    'requester_email' => $authorEmail,
                    'last_message_at' => $now,
                    'created_at' => $now,
                    'updated_at' => $now,
                ]
            );

            $this->appendMessage(
                $pdo,
                $tenantId,
                $requestId,
                [
                    'author_user_id' => $authorUserId !== '' ? $authorUserId : null,
                    'author_tenant_user_id' => $authorTenantUserId !== '' ? $authorTenantUserId : null,
                    'author_name' => $authorName,
                    'author_role' => $this->resolveRoleLabel($auth),
                    'message_kind' => 'request_created',
                    'body' => $message,
                    'new_status' => 'new',
                    'created_at' => $now,
                ]
            );

            $pdo->commit();
        } catch (\Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            throw $exception;
        }

        $created = $this->find($pdo, $auth, $requestId);

        if ($created === null) {
            throw new RuntimeException('Der Support-Vorgang konnte nicht geladen werden.');
        }

        return $created;
    }

    public function addMessage(PDO $pdo, array $auth, string $requestId, array $input): SupportRequest
    {
        $request = $this->find($pdo, $auth, $requestId);

        if ($request === null) {
            throw new RuntimeException('Der ausgewählte Support-Vorgang wurde nicht gefunden.');
        }

        $body = trim((string) ($input['body'] ?? ''));
        $messageKind = trim((string) ($input['message_kind'] ?? 'public_reply'));

        if ($body === '') {
            throw new RuntimeException('Bitte gib eine Nachricht an.');
        }

        if (!$this->canManage($auth) && !$this->canReply($auth, $request)) {
            throw new RuntimeException('Für diesen Support-Vorgang hast du keine Schreibrechte.');
        }

        if ($messageKind !== 'internal_note' && $messageKind !== 'public_reply') {
            $messageKind = $this->canManage($auth) ? 'internal_note' : 'public_reply';
        }

        if (!$this->canManage($auth)) {
            $messageKind = 'public_reply';
        }

        $now = date('Y-m-d H:i:s');
        $authorName = trim((string) ($auth['display_name'] ?? $auth['email'] ?? ''));

        if ($authorName === '') {
            $authorName = 'Unbekannt';
        }

        $pdo->beginTransaction();

        try {
            $this->appendMessage(
                $pdo,
                (string) $request->tenantId,
                $request->id,
                [
                    'author_user_id' => (string) ($auth['user_id'] ?? '') !== '' ? (string) ($auth['user_id'] ?? '') : null,
                    'author_tenant_user_id' => (string) ($auth['tenant_user_id'] ?? '') !== '' ? (string) ($auth['tenant_user_id'] ?? '') : null,
                    'author_name' => $authorName,
                    'author_role' => $this->resolveRoleLabel($auth),
                    'message_kind' => $messageKind,
                    'body' => $body,
                    'new_status' => null,
                    'created_at' => $now,
                ]
            );

            $this->execute(
                $pdo,
                'UPDATE support_requests SET last_message_at = :last_message_at, updated_at = :updated_at WHERE id = :id',
                [
                    'last_message_at' => $now,
                    'updated_at' => $now,
                    'id' => $request->id,
                ]
            );

            $pdo->commit();
        } catch (\Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            throw $exception;
        }

        $updated = $this->find($pdo, $auth, $request->id);

        if ($updated === null) {
            throw new RuntimeException('Der Support-Vorgang konnte nach der Antwort nicht geladen werden.');
        }

        return $updated;
    }

    public function updateRequest(PDO $pdo, array $auth, string $requestId, array $input): SupportRequest
    {
        if (!$this->canManage($auth)) {
            throw new RuntimeException('Nur Verantwortliche dürfen Support-Vorgänge bearbeiten.');
        }

        $request = $this->find($pdo, $auth, $requestId);

        if ($request === null) {
            throw new RuntimeException('Der ausgewählte Support-Vorgang wurde nicht gefunden.');
        }

        $status = trim((string) ($input['status'] ?? $request->status));
        $routeTarget = trim((string) ($input['route_target'] ?? $request->routeTarget));
        $note = trim((string) ($input['note'] ?? ''));

        if (!in_array($status, self::STATUSES, true)) {
            throw new RuntimeException('Bitte wähle einen gültigen Status.');
        }

        if (!in_array($routeTarget, self::ROUTE_TARGETS, true)) {
            $routeTarget = $request->routeTarget;
        }

        $authorName = trim((string) ($auth['display_name'] ?? $auth['email'] ?? ''));

        if ($authorName === '') {
            $authorName = 'Verantwortlich';
        }

        $now = date('Y-m-d H:i:s');
        $resolvedAt = in_array($status, ['resolved', 'closed'], true) ? $now : null;
        $closedAt = $status === 'closed' ? $now : null;
        $assigneeUserId = (string) ($auth['user_id'] ?? '') !== '' ? (string) ($auth['user_id'] ?? '') : null;
        $assigneeTenantUserId = (string) ($auth['tenant_user_id'] ?? '') !== '' ? (string) ($auth['tenant_user_id'] ?? '') : null;

        $pdo->beginTransaction();

        try {
            $this->execute(
                $pdo,
                <<<'SQL'
UPDATE support_requests
SET status = :status,
    route_target = :route_target,
    assigned_to_user_id = :assigned_to_user_id,
    assigned_to_tenant_user_id = :assigned_to_tenant_user_id,
    resolved_at = :resolved_at,
    closed_at = :closed_at,
    last_message_at = :last_message_at,
    updated_at = :updated_at
WHERE id = :id
SQL,
                [
                    'status' => $status,
                    'route_target' => $routeTarget,
                    'assigned_to_user_id' => $assigneeUserId,
                    'assigned_to_tenant_user_id' => $assigneeTenantUserId,
                    'resolved_at' => $resolvedAt,
                    'closed_at' => $closedAt,
                    'last_message_at' => $now,
                    'updated_at' => $now,
                    'id' => $request->id,
                ]
            );

            $this->appendMessage(
                $pdo,
                (string) $request->tenantId,
                $request->id,
                [
                    'author_user_id' => $assigneeUserId,
                    'author_tenant_user_id' => $assigneeTenantUserId,
                    'author_name' => $authorName,
                    'author_role' => $this->resolveRoleLabel($auth),
                    'message_kind' => 'status_change',
                    'body' => $note !== '' ? $note : 'Status auf ' . $status . ' gesetzt.',
                    'new_status' => $status,
                    'created_at' => $now,
                ]
            );

            $pdo->commit();
        } catch (\Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            throw $exception;
        }

        $updated = $this->find($pdo, $auth, $request->id);

        if ($updated === null) {
            throw new RuntimeException('Der Support-Vorgang konnte nach dem Update nicht geladen werden.');
        }

        return $updated;
    }

    /**
     * @param array<string, mixed> $row
     */
    private function hydrateRequest(PDO $pdo, array $row): SupportRequest
    {
        $messages = array_map(
            static fn(array $messageRow): SupportRequestMessage => SupportRequestMessage::fromRow($messageRow),
            $this->queryAll(
                $pdo,
                'SELECT * FROM support_request_messages WHERE support_request_id = :support_request_id ORDER BY created_at ASC',
                ['support_request_id' => (string) ($row['id'] ?? '')]
            )
        );

        return SupportRequest::fromRow($row, $messages);
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function appendMessage(PDO $pdo, string $tenantId, string $requestId, array $payload): void
    {
        $this->execute(
            $pdo,
            <<<'SQL'
INSERT INTO support_request_messages (
    id, support_request_id, tenant_id, author_user_id, author_tenant_user_id,
    author_name, author_role, message_kind, new_status, body, created_at
) VALUES (
    :id, :support_request_id, :tenant_id, :author_user_id, :author_tenant_user_id,
    :author_name, :author_role, :message_kind, :new_status, :body, :created_at
)
SQL,
            [
                'id' => app_uuid(),
                'support_request_id' => $requestId,
                'tenant_id' => $tenantId,
                'author_user_id' => $payload['author_user_id'] ?? null,
                'author_tenant_user_id' => $payload['author_tenant_user_id'] ?? null,
                'author_name' => $payload['author_name'] ?? 'Unbekannt',
                'author_role' => $payload['author_role'] ?? 'member',
                'message_kind' => $payload['message_kind'] ?? 'public_reply',
                'new_status' => $payload['new_status'] ?? null,
                'body' => $payload['body'] ?? '',
                'created_at' => $payload['created_at'] ?? date('Y-m-d H:i:s'),
            ]
        );
    }

    private function resolveRoleLabel(array $auth): string
    {
        if ($this->isPlatformAdmin($auth)) {
            return 'platform_admin';
        }

        $roles = array_map('strval', $auth['roles'] ?? []);

        foreach (['tenant_admin', 'support_contact', 'finance_admin', 'survey_manager'] as $role) {
            if (in_array($role, $roles, true)) {
                return $role;
            }
        }

        return 'member';
    }

    private function hasRole(array $auth, string $role): bool
    {
        return in_array($role, array_map('strval', $auth['roles'] ?? []), true);
    }

    private function isPlatformAdmin(array $auth): bool
    {
        return !empty($auth['is_platform_admin']);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function queryAll(PDO $pdo, string $sql, array $params = []): array
    {
        $statement = $pdo->prepare($sql);
        $statement->execute($params);

        return $statement->fetchAll() ?: [];
    }

    /**
     * @return array<string, mixed>|null
     */
    private function queryOne(PDO $pdo, string $sql, array $params = []): ?array
    {
        $statement = $pdo->prepare($sql);
        $statement->execute($params);
        $row = $statement->fetch();

        return is_array($row) ? $row : null;
    }

    private function execute(PDO $pdo, string $sql, array $params = []): void
    {
        $statement = $pdo->prepare($sql);
        $statement->execute($params);
    }
}
