package de.cannamanage.service;

import de.cannamanage.domain.entity.*;
import de.cannamanage.domain.enums.*;
import de.cannamanage.service.repository.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;

/**
 * Forum service — CRUD, moderation, reactions, reports, and notifications for the club forum.
 * Key constraints:
 * - 60-minute edit window for replies
 * - Reporter identity protected (not exposed in public DTOs)
 * - Locked topics: no new replies, existing content visible
 * - Reactions: one per user per target (toggle behavior)
 * - All content is club-scoped (tenant isolation via club_id)
 */
@Service
@Transactional
public class ForumService {

    private static final Logger log = LoggerFactory.getLogger(ForumService.class);
    private static final Duration EDIT_WINDOW = Duration.ofMinutes(60);

    private final ForumTopicRepository topicRepository;
    private final ForumReplyRepository replyRepository;
    private final ForumReactionRepository reactionRepository;
    private final ForumReportRepository reportRepository;
    private final MemberRepository memberRepository;
    private final NotificationService notificationService;
    private final AuditService auditService;

    public ForumService(ForumTopicRepository topicRepository,
                       ForumReplyRepository replyRepository,
                       ForumReactionRepository reactionRepository,
                       ForumReportRepository reportRepository,
                       MemberRepository memberRepository,
                       NotificationService notificationService,
                       AuditService auditService) {
        this.topicRepository = topicRepository;
        this.replyRepository = replyRepository;
        this.reactionRepository = reactionRepository;
        this.reportRepository = reportRepository;
        this.memberRepository = memberRepository;
        this.notificationService = notificationService;
        this.auditService = auditService;
    }

    // ---- Topics ----

    public ForumTopic createTopic(UUID clubId, String title, String content, UUID authorId) {
        var topic = new ForumTopic(clubId, title, content, authorId);
        ForumTopic saved = topicRepository.save(topic);
        log.info("Forum topic created: '{}' by {} in club {}", title, authorId, clubId);

        auditService.logEvent(AuditEventType.FORUM_TOPIC_CREATED, authorId,
                "Forum topic created: " + title, saved.getId().toString());

        // Notify club members about new topic
        try {
            var members = memberRepository.findByClubId(clubId);
            members.forEach(member -> {
                if (member.getUserId() != null && !member.getUserId().equals(authorId)) {
                    notificationService.sendNotification(
                            member.getUserId(),
                            NotificationType.FORUM_REPLY,
                            "Neues Thema: " + title,
                            "Ein neues Thema wurde im Forum erstellt.",
                            "/portal/forum/" + saved.getId()
                    );
                }
            });
        } catch (Exception e) {
            log.warn("Failed to send forum topic notifications: {}", e.getMessage());
        }

        return saved;
    }

    @Transactional(readOnly = true)
    public Optional<ForumTopic> getTopic(UUID topicId) {
        return topicRepository.findById(topicId);
    }

    @Transactional(readOnly = true)
    public Page<ForumTopic> getTopics(UUID clubId, int page, int size) {
        return topicRepository.findByClubIdOrderByPinnedDescLastReplyAtDescCreatedAtDesc(
                clubId, PageRequest.of(page, size));
    }

    public ForumTopic lockTopic(UUID topicId, UUID moderatorId) {
        ForumTopic topic = topicRepository.findById(topicId)
                .orElseThrow(() -> new IllegalArgumentException("Topic not found: " + topicId));
        topic.setLocked(true);
        auditService.logEvent(AuditEventType.FORUM_TOPIC_LOCKED, moderatorId,
                "Topic locked: " + topic.getTitle(), topicId.toString());
        log.info("Forum topic locked: {} by moderator {}", topicId, moderatorId);
        return topicRepository.save(topic);
    }

    public ForumTopic unlockTopic(UUID topicId, UUID moderatorId) {
        ForumTopic topic = topicRepository.findById(topicId)
                .orElseThrow(() -> new IllegalArgumentException("Topic not found: " + topicId));
        topic.setLocked(false);
        log.info("Forum topic unlocked: {} by moderator {}", topicId, moderatorId);
        return topicRepository.save(topic);
    }

    public ForumTopic pinTopic(UUID topicId, UUID moderatorId) {
        ForumTopic topic = topicRepository.findById(topicId)
                .orElseThrow(() -> new IllegalArgumentException("Topic not found: " + topicId));
        topic.setPinned(true);
        log.info("Forum topic pinned: {} by moderator {}", topicId, moderatorId);
        return topicRepository.save(topic);
    }

    public ForumTopic unpinTopic(UUID topicId, UUID moderatorId) {
        ForumTopic topic = topicRepository.findById(topicId)
                .orElseThrow(() -> new IllegalArgumentException("Topic not found: " + topicId));
        topic.setPinned(false);
        log.info("Forum topic unpinned: {} by moderator {}", topicId, moderatorId);
        return topicRepository.save(topic);
    }

    public void deleteTopic(UUID topicId, UUID moderatorId, String reason) {
        ForumTopic topic = topicRepository.findById(topicId)
                .orElseThrow(() -> new IllegalArgumentException("Topic not found: " + topicId));

        // Notify the author about deletion
        if (reason != null && !reason.isBlank()) {
            notificationService.sendNotification(
                    topic.getAuthorId(),
                    NotificationType.FORUM_REPLY,
                    "Dein Thema wurde entfernt",
                    "Grund: " + reason,
                    "/portal/forum"
            );
        }

        auditService.logEvent(AuditEventType.FORUM_TOPIC_DELETED, moderatorId,
                "Topic deleted: " + topic.getTitle() + " (reason: " + reason + ")", topicId.toString());

        replyRepository.deleteByTopicId(topicId);
        topicRepository.delete(topic);
        log.info("Forum topic deleted: {} by moderator {} (reason: {})", topicId, moderatorId, reason);
    }

    // ---- Replies ----

    public ForumReply createReply(UUID topicId, String content, UUID authorId) {
        ForumTopic topic = topicRepository.findById(topicId)
                .orElseThrow(() -> new IllegalArgumentException("Topic not found: " + topicId));

        if (topic.isLocked()) {
            throw new IllegalStateException("Cannot reply to a locked topic");
        }

        var reply = new ForumReply(topicId, topic.getClubId(), content, authorId);
        ForumReply saved = replyRepository.save(reply);

        // Update topic reply count and last reply timestamp
        topic.setReplyCount(topic.getReplyCount() + 1);
        topic.setLastReplyAt(Instant.now());
        topicRepository.save(topic);

        log.info("Forum reply created on topic {} by {}", topicId, authorId);

        auditService.logEvent(AuditEventType.FORUM_REPLY_CREATED, authorId,
                "Reply to topic: " + topic.getTitle(), saved.getId().toString());

        // Notify topic author about new reply
        if (!topic.getAuthorId().equals(authorId)) {
            notificationService.sendNotification(
                    topic.getAuthorId(),
                    NotificationType.FORUM_REPLY,
                    "Neue Antwort auf: " + topic.getTitle(),
                    "Jemand hat auf dein Thema geantwortet.",
                    "/portal/forum/" + topicId
            );
        }

        return saved;
    }

    public ForumReply editReply(UUID replyId, String content, UUID authorId) {
        ForumReply reply = replyRepository.findById(replyId)
                .orElseThrow(() -> new IllegalArgumentException("Reply not found: " + replyId));

        if (!reply.getAuthorId().equals(authorId)) {
            throw new IllegalStateException("Only the author can edit their reply");
        }

        // 60-minute edit window
        if (Duration.between(reply.getCreatedAt(), Instant.now()).compareTo(EDIT_WINDOW) > 0) {
            throw new IllegalStateException("Edit window (60 minutes) has expired");
        }

        reply.setContent(content);
        reply.setEdited(true);
        reply.setEditedAt(Instant.now());
        log.info("Forum reply edited: {} by {}", replyId, authorId);
        return replyRepository.save(reply);
    }

    public void deleteReply(UUID replyId, UUID moderatorId) {
        ForumReply reply = replyRepository.findById(replyId)
                .orElseThrow(() -> new IllegalArgumentException("Reply not found: " + replyId));

        // Update topic reply count
        topicRepository.findById(reply.getTopicId()).ifPresent(topic -> {
            topic.setReplyCount(Math.max(0, topic.getReplyCount() - 1));
            topicRepository.save(topic);
        });

        auditService.logEvent(AuditEventType.FORUM_REPLY_DELETED, moderatorId,
                "Reply deleted from topic", replyId.toString());

        replyRepository.delete(reply);
        log.info("Forum reply deleted: {} by moderator {}", replyId, moderatorId);
    }

    @Transactional(readOnly = true)
    public Page<ForumReply> getReplies(UUID topicId, int page, int size) {
        return replyRepository.findByTopicIdOrderByCreatedAtAsc(topicId, PageRequest.of(page, size));
    }

    // ---- Reactions ----

    /**
     * Toggle a reaction — if the same reaction exists, remove it; otherwise add/replace it.
     * Returns the reaction if added, empty if removed.
     */
    public Optional<ForumReaction> toggleReaction(ForumTargetType targetType, UUID targetId,
                                                   UUID userId, ReactionType reactionType) {
        Optional<ForumReaction> existing = reactionRepository
                .findByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId);

        if (existing.isPresent()) {
            ForumReaction current = existing.get();
            if (current.getReactionType() == reactionType) {
                // Same reaction — remove (toggle off)
                reactionRepository.delete(current);
                return Optional.empty();
            } else {
                // Different reaction — update
                current.setReactionType(reactionType);
                return Optional.of(reactionRepository.save(current));
            }
        } else {
            // No existing reaction — create new
            var reaction = new ForumReaction(targetType, targetId, userId, reactionType);
            return Optional.of(reactionRepository.save(reaction));
        }
    }

    @Transactional(readOnly = true)
    public long getReactionCount(ForumTargetType targetType, UUID targetId, ReactionType reactionType) {
        return reactionRepository.countByTargetTypeAndTargetIdAndReactionType(targetType, targetId, reactionType);
    }

    // ---- Reports ----

    public ForumReport reportContent(UUID clubId, ForumTargetType targetType, UUID targetId,
                                      UUID reporterId, String reason) {
        var report = new ForumReport(clubId, targetType, targetId, reporterId, reason);
        ForumReport saved = reportRepository.save(report);
        log.info("Content reported: {} {} by {} (reason: {})", targetType, targetId, reporterId, reason);
        return saved;
    }

    @Transactional(readOnly = true)
    public Page<ForumReport> getReports(UUID clubId, ReportStatus status, int page, int size) {
        return reportRepository.findByClubIdAndStatusOrderByCreatedAtDesc(
                clubId, status, PageRequest.of(page, size));
    }

    @Transactional(readOnly = true)
    public long getOpenReportCount(UUID clubId) {
        return reportRepository.countByClubIdAndStatus(clubId, ReportStatus.OPEN);
    }

    public ForumReport reviewReport(UUID reportId, UUID reviewerId, ReportStatus status) {
        ForumReport report = reportRepository.findById(reportId)
                .orElseThrow(() -> new IllegalArgumentException("Report not found: " + reportId));

        report.setStatus(status);
        report.setReviewedBy(reviewerId);
        report.setReviewedAt(Instant.now());

        auditService.logEvent(AuditEventType.FORUM_REPORT_REVIEWED, reviewerId,
                "Report reviewed: " + status + " for " + report.getTargetType() + " " + report.getTargetId(),
                reportId.toString());

        log.info("Report {} reviewed by {}: {}", reportId, reviewerId, status);
        return reportRepository.save(report);
    }
}
