package pl.polskalokalnie.notification;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

/**
 * Centralny serwis powiadomien. Zapisuje powiadomienia per uzytkownik i wypycha je w czasie
 * rzeczywistym przez SSE. Inne moduly (wiadomosci, ogloszenia, administracja) wolaja {@link #create}.
 */
@Service
public class NotificationService {

    private static final long SSE_TIMEOUT_MS = 30L * 60L * 1000L;

    private final NotificationRepository repository;

    // email uzytkownika -> otwarte polaczenia SSE (moze byc kilka kart/urzadzen).
    private final Map<String, CopyOnWriteArrayList<SseEmitter>> emitters = new ConcurrentHashMap<>();

    public NotificationService(NotificationRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public Notification create(String userEmail, String category, String icon, String title,
            String body, String link, String dedupeKey) {
        if (userEmail == null || userEmail.isBlank() || title == null || title.isBlank()) {
            return null;
        }
        if (dedupeKey != null && !dedupeKey.isBlank()
                && repository.existsByUserEmailIgnoreCaseAndDedupeKey(userEmail, dedupeKey)) {
            return null;
        }
        Notification notification = new Notification();
        notification.setUserEmail(userEmail);
        notification.setCategory(normalizeCategory(category));
        notification.setIcon(icon == null || icon.isBlank() ? defaultIcon(category) : icon);
        notification.setTitle(title.trim());
        notification.setBody(body == null ? null : body.trim());
        notification.setLink(link == null || link.isBlank() ? null : link.trim());
        notification.setDedupeKey(dedupeKey == null || dedupeKey.isBlank() ? null : dedupeKey.trim());

        Notification saved = repository.save(notification);
        push(userEmail, NotificationResponse.from(saved));
        return saved;
    }

    public List<NotificationResponse> listOwn(String email) {
        return repository.findTop100ByUserEmailIgnoreCaseOrderByCreatedAtDesc(email).stream()
                .map(NotificationResponse::from)
                .toList();
    }

    public long unreadCount(String email) {
        return repository.countByUserEmailIgnoreCaseAndReadFalse(email);
    }

    @Transactional
    public void markRead(Long id, String email) {
        Notification notification = repository.findById(id)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Powiadomienie nie istnieje"));
        if (email == null || !email.equalsIgnoreCase(notification.getUserEmail())) {
            throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Brak dostepu do tego powiadomienia");
        }
        if (!notification.isRead()) {
            notification.setRead(true);
            repository.save(notification);
        }
    }

    @Transactional
    public void markAllRead(String email) {
        repository.markAllRead(email);
    }

    // --- SSE ---

    public SseEmitter subscribe(String email) {
        SseEmitter emitter = new SseEmitter(SSE_TIMEOUT_MS);
        CopyOnWriteArrayList<SseEmitter> list = emitters.computeIfAbsent(email, key -> new CopyOnWriteArrayList<>());
        list.add(emitter);

        emitter.onCompletion(() -> remove(email, emitter));
        emitter.onTimeout(() -> remove(email, emitter));
        emitter.onError(error -> remove(email, emitter));

        try {
            emitter.send(SseEmitter.event().name("ready").data("ok"));
        } catch (IOException ex) {
            remove(email, emitter);
        }
        return emitter;
    }

    private void push(String email, NotificationResponse payload) {
        CopyOnWriteArrayList<SseEmitter> list = emitters.get(email);
        if (list == null) {
            return;
        }
        for (SseEmitter emitter : list) {
            try {
                emitter.send(SseEmitter.event().name("notification").data(payload));
            } catch (Exception ex) {
                remove(email, emitter);
            }
        }
    }

    private void remove(String email, SseEmitter emitter) {
        CopyOnWriteArrayList<SseEmitter> list = emitters.get(email);
        if (list != null) {
            list.remove(emitter);
            if (list.isEmpty()) {
                emitters.remove(email, list);
            }
        }
    }

    private String normalizeCategory(String category) {
        if ("listing".equals(category) || "messages".equals(category) || "system".equals(category)) {
            return category;
        }
        return "system";
    }

    private String defaultIcon(String category) {
        if ("messages".equals(category)) {
            return "message";
        }
        if ("listing".equals(category)) {
            return "house";
        }
        return "bell";
    }
}
