<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\FileStore;

class SupportService
{
    private FileStore $tickets;
    private FileStore $replies;
    private FileStore $notifications;
    private ActivityLogService $logs;

    public function __construct()
    {
        $this->tickets = new FileStore(storage_path('data/support_tickets.json'));
        $this->replies = new FileStore(storage_path('data/support_replies.json'));
        $this->notifications = new FileStore(storage_path('data/notifications.json'));
        $this->logs = new ActivityLogService();
    }

    public function categories(): array
    {
        return [
            'payment' => 'Pagamentos',
            'access' => 'Acesso ao curso',
            'certificate' => 'Certificados',
            'account' => 'Conta e perfil',
            'content' => 'Conteudo do curso',
            'technical' => 'Problema tecnico',
            'other' => 'Outro assunto',
        ];
    }

    public function statuses(): array
    {
        return [
            'open' => 'Aberto',
            'in_review' => 'Em analise',
            'answered' => 'Respondido',
            'closed' => 'Fechado',
        ];
    }

    public function ticketsForUser(array $user, array $filters = []): array
    {
        $items = $this->tickets->all();

        if (($user['role'] ?? '') !== 'admin') {
            $items = array_filter($items, fn (array $ticket): bool => (int) ($ticket['user_id'] ?? 0) === (int) ($user['id'] ?? 0));
        }

        return $this->filterTickets($items, $filters);
    }

    public function create(array $data, array $user, array $file = []): array
    {
        $subject = trim((string) ($data['subject'] ?? ''));
        $message = trim((string) ($data['message'] ?? ''));
        $category = (string) ($data['category'] ?? 'other');
        $priority = (string) ($data['priority'] ?? 'normal');

        if ($subject === '' || $message === '') {
            return ['ok' => false, 'error' => 'Preencha o assunto e a mensagem.'];
        }

        if (strlen($subject) > 140) {
            return ['ok' => false, 'error' => 'Assunto demasiado longo.'];
        }

        if (strlen($message) > 4000) {
            return ['ok' => false, 'error' => 'Mensagem demasiado longa.'];
        }

        if (!array_key_exists($category, $this->categories())) {
            $category = 'other';
        }

        if (!in_array($priority, ['low', 'normal', 'high', 'urgent'], true)) {
            $priority = 'normal';
        }

        $attachment = $this->storeAttachment($file);

        if (!$attachment['ok']) {
            return ['ok' => false, 'error' => $attachment['error']];
        }

        $ticket = $this->tickets->insert([
            'ticket_number' => 'SUP-' . date('Ymd') . '-' . strtoupper(substr(bin2hex(random_bytes(3)), 0, 6)),
            'user_id' => (int) $user['id'],
            'user_name' => $user['name'] ?? 'Utilizador',
            'user_email' => $user['email'] ?? '',
            'role' => $user['role'] ?? '',
            'category' => $category,
            'priority' => $priority,
            'subject' => $subject,
            'message' => $message,
            'attachment' => $attachment['attachment'],
            'status' => 'open',
            'last_reply_by' => (int) $user['id'],
            'last_reply_role' => $user['role'] ?? '',
            'closed_at' => null,
            'created_at' => date('c'),
            'updated_at' => date('c'),
        ]);

        $this->notifyAdmins('Novo ticket de suporte', "{$ticket['user_name']} abriu {$ticket['ticket_number']}.", '/admin/support/show?id=' . $ticket['id']);
        $this->logs->log('support_ticket_created', "Ticket {$ticket['ticket_number']} criado.", (int) $user['id']);

        return ['ok' => true, 'ticket' => $ticket];
    }

    public function find(int $id): ?array
    {
        foreach ($this->tickets->all() as $ticket) {
            if ((int) ($ticket['id'] ?? 0) === $id) {
                $ticket['replies'] = $this->repliesForTicket($id);
                return $ticket;
            }
        }

        return null;
    }

    public function reply(int $ticketId, array $user, string $message, array $file = []): array
    {
        $ticket = $this->find($ticketId);

        if (!$ticket) {
            return ['ok' => false, 'error' => 'Ticket nao encontrado.'];
        }

        if (!$this->canView($ticket, $user)) {
            return ['ok' => false, 'error' => 'Nao tem permissao para responder este ticket.'];
        }

        if (($ticket['status'] ?? '') === 'closed') {
            return ['ok' => false, 'error' => 'Este ticket ja esta fechado.'];
        }

        $message = trim($message);

        if ($message === '') {
            return ['ok' => false, 'error' => 'Escreva uma resposta.'];
        }

        if (strlen($message) > 4000) {
            return ['ok' => false, 'error' => 'Resposta demasiado longa.'];
        }

        $attachment = $this->storeAttachment($file);

        if (!$attachment['ok']) {
            return ['ok' => false, 'error' => $attachment['error']];
        }

        $reply = $this->replies->insert([
            'ticket_id' => $ticketId,
            'user_id' => (int) $user['id'],
            'user_name' => $user['name'] ?? 'Utilizador',
            'user_role' => $user['role'] ?? '',
            'message' => $message,
            'attachment' => $attachment['attachment'],
            'created_at' => date('c'),
        ]);

        $status = ($user['role'] ?? '') === 'admin' ? 'answered' : 'open';
        $this->updateTicket($ticketId, [
            'status' => $status,
            'last_reply_by' => (int) $user['id'],
            'last_reply_role' => $user['role'] ?? '',
            'updated_at' => date('c'),
        ]);

        if (($user['role'] ?? '') === 'admin') {
            $this->notify((int) $ticket['user_id'], 'Ticket respondido', "O suporte respondeu {$ticket['ticket_number']}.", '/support/show?id=' . $ticketId);
        } else {
            $this->notifyAdmins('Nova resposta no suporte', "{$user['name']} respondeu {$ticket['ticket_number']}.", '/admin/support/show?id=' . $ticketId);
        }

        $this->logs->log('support_ticket_replied', "Resposta adicionada ao ticket {$ticket['ticket_number']}.", (int) $user['id']);

        return ['ok' => true, 'reply' => $reply];
    }

    public function changeStatus(int $ticketId, string $status, array $admin): array
    {
        if (($admin['role'] ?? '') !== 'admin') {
            return ['ok' => false, 'error' => 'Apenas administradores podem alterar estado.'];
        }

        if (!array_key_exists($status, $this->statuses())) {
            return ['ok' => false, 'error' => 'Estado invalido.'];
        }

        $ticket = $this->find($ticketId);

        if (!$ticket) {
            return ['ok' => false, 'error' => 'Ticket nao encontrado.'];
        }

        $updated = $this->updateTicket($ticketId, [
            'status' => $status,
            'closed_at' => $status === 'closed' ? date('c') : null,
            'updated_at' => date('c'),
        ]);

        $this->notify((int) $ticket['user_id'], 'Estado do ticket actualizado', "{$ticket['ticket_number']} esta agora {$this->statuses()[$status]}.", '/support/show?id=' . $ticketId);
        $this->logs->log('support_ticket_status_changed', "Ticket {$ticket['ticket_number']} alterado para {$status}.", (int) $admin['id']);

        return ['ok' => true, 'ticket' => $updated];
    }

    public function canView(array $ticket, array $user): bool
    {
        if (($user['role'] ?? '') === 'admin') {
            return true;
        }

        return (int) ($ticket['user_id'] ?? 0) === (int) ($user['id'] ?? 0);
    }

    public function attachmentPath(array $attachment): string
    {
        return storage_path((string) ($attachment['path'] ?? ''));
    }

    private function repliesForTicket(int $ticketId): array
    {
        $items = array_filter($this->replies->all(), fn (array $reply): bool => (int) ($reply['ticket_id'] ?? 0) === $ticketId);
        usort($items, fn (array $a, array $b): int => strcmp($a['created_at'] ?? '', $b['created_at'] ?? ''));

        return array_values($items);
    }

    private function filterTickets(iterable $items, array $filters): array
    {
        $items = array_values(is_array($items) ? $items : iterator_to_array($items));
        $status = (string) ($filters['status'] ?? '');
        $category = (string) ($filters['category'] ?? '');
        $search = strtolower(trim((string) ($filters['search'] ?? '')));

        if ($status !== '') {
            $items = array_filter($items, fn (array $ticket): bool => ($ticket['status'] ?? '') === $status);
        }

        if ($category !== '') {
            $items = array_filter($items, fn (array $ticket): bool => ($ticket['category'] ?? '') === $category);
        }

        if ($search !== '') {
            $items = array_filter($items, function (array $ticket) use ($search): bool {
                $haystack = strtolower(
                    ($ticket['ticket_number'] ?? '') . ' ' .
                    ($ticket['subject'] ?? '') . ' ' .
                    ($ticket['message'] ?? '') . ' ' .
                    ($ticket['user_name'] ?? '') . ' ' .
                    ($ticket['user_email'] ?? '')
                );

                return str_contains($haystack, $search);
            });
        }

        usort($items, fn (array $a, array $b): int => strcmp($b['updated_at'] ?? '', $a['updated_at'] ?? ''));

        return array_values($items);
    }

    private function updateTicket(int $ticketId, array $changes): array
    {
        $records = $this->tickets->all();
        $updated = [];

        foreach ($records as $index => $ticket) {
            if ((int) ($ticket['id'] ?? 0) === $ticketId) {
                $records[$index] = array_merge($ticket, $changes);
                $updated = $records[$index];
                break;
            }
        }

        $this->tickets->save($records);

        return $updated;
    }

    private function storeAttachment(array $file): array
    {
        if (!$file || ($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
            return ['ok' => true, 'attachment' => null];
        }

        if (($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) {
            return ['ok' => false, 'error' => 'Falha no envio do anexo.'];
        }

        if ((int) ($file['size'] ?? 0) > 8 * 1024 * 1024) {
            return ['ok' => false, 'error' => 'Anexo acima de 8MB.'];
        }

        $mimeType = mime_content_type((string) $file['tmp_name']) ?: ($file['type'] ?? '');
        $allowed = ['application/pdf', 'image/jpeg', 'image/png'];

        if (!in_array($mimeType, $allowed, true)) {
            return ['ok' => false, 'error' => 'Anexo deve ser PDF, JPG ou PNG.'];
        }

        $extension = strtolower(pathinfo((string) $file['name'], PATHINFO_EXTENSION));
        $safeName = bin2hex(random_bytes(16)) . ($extension ? '.' . $extension : '');
        $relativePath = 'support' . DIRECTORY_SEPARATOR . $safeName;
        $destination = storage_path($relativePath);

        if (!is_dir(dirname($destination))) {
            mkdir(dirname($destination), 0775, true);
        }

        if (!move_uploaded_file((string) $file['tmp_name'], $destination)) {
            return ['ok' => false, 'error' => 'Nao foi possivel guardar o anexo.'];
        }

        return [
            'ok' => true,
            'attachment' => [
                'path' => $relativePath,
                'original_name' => (string) $file['name'],
                'mime_type' => $mimeType,
                'size' => (int) $file['size'],
            ],
        ];
    }

    private function notifyAdmins(string $title, string $body, string $url): void
    {
        $auth = new AuthService();

        foreach ($auth->users(['role' => 'admin', 'status' => 'active']) as $admin) {
            $this->notify((int) $admin['id'], $title, $body, $url);
        }
    }

    private function notify(int $userId, string $title, string $body, string $url): void
    {
        if ($userId <= 0) {
            return;
        }

        $this->notifications->insert([
            'user_id' => $userId,
            'title' => $title,
            'body' => $body,
            'url' => $url,
            'read_at' => null,
            'created_at' => date('c'),
            'updated_at' => date('c'),
        ]);
    }
}
