package de.oaa.xxx.games.chastity.common;

import de.oaa.xxx.games.chastity.cardlock.CardlockTemplateEntity;
import de.oaa.xxx.games.chastity.cardlock.CardlockTemplateRepository;
import de.oaa.xxx.games.chastity.timelock.TimeLockTemplateEntity;
import de.oaa.xxx.games.chastity.timelock.TimeLockTemplateRepository;
import de.oaa.xxx.games.chastity.tasks.TaskMode;
import de.oaa.xxx.user.UserRepository;
import de.oaa.xxx.user.UserService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/templates")
public class TemplateExploreController {

    private final BaseLockTemplateRepository templateRepository;
    private final TemplateSubscriptionRepository subscriptionRepository;
    private final TimeLockTemplateRepository timeLockTemplateRepository;
    private final CardlockTemplateRepository cardlockTemplateRepository;
    private final UserRepository userRepository;
    private final UserService userService;

    public TemplateExploreController(BaseLockTemplateRepository templateRepository,
                                     TemplateSubscriptionRepository subscriptionRepository,
                                     TimeLockTemplateRepository timeLockTemplateRepository,
                                     CardlockTemplateRepository cardlockTemplateRepository,
                                     UserRepository userRepository,
                                     UserService userService) {
        this.templateRepository = templateRepository;
        this.subscriptionRepository = subscriptionRepository;
        this.timeLockTemplateRepository = timeLockTemplateRepository;
        this.cardlockTemplateRepository = cardlockTemplateRepository;
        this.userRepository = userRepository;
        this.userService = userService;
    }

    // ── Öffentliche Vorlagen entdecken ────────────────────────────────────────

    @GetMapping("/public")
    public ResponseEntity<Map<String, Object>> getPublicTemplates(
            @RequestParam(name = "page", defaultValue = "0") int page,
            @RequestParam(name = "size", defaultValue = "10") int size,
            @RequestParam(defaultValue = "") String q,
            Principal principal) {

        UUID myId = userService.requireUser(principal).getUserId();

        size = Math.min(size, 20);
        var pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "subscriberCount"));

        Page<BaseLockTemplateEntity> pageResult = q.isBlank()
                ? templateRepository.findByPublishedTrue(pageable)
                : templateRepository.findByPublishedTrueAndNameContainingIgnoreCase(q.trim(), pageable);

        Set<UUID> subscribedIds = subscriptionRepository.findByUserId(myId)
                .stream().map(TemplateSubscriptionEntity::getTemplateId).collect(Collectors.toSet());

        List<Map<String, Object>> content = pageResult.getContent().stream()
                .map(t -> toPublicDto(t, myId, subscribedIds))
                .toList();

        return ResponseEntity.ok(Map.of(
                "content", content,
                "page", pageResult.getNumber(),
                "hasMore", !pageResult.isLast()
        ));
    }

    // ── Einzelne Vorlage per ID (für Detail-Dialog) ──────────────────────────

    @GetMapping("/{id}/public")
    public ResponseEntity<Map<String, Object>> getTemplate(
            @PathVariable("id") UUID id, Principal principal) {
        UUID myId = userService.requireUser(principal).getUserId();

        var tOpt = templateRepository.findById(id);
        if (tOpt.isEmpty()) return ResponseEntity.notFound().build();
        var t = tOpt.get();

        // Sichtbar wenn: eigene Vorlage ODER veröffentlicht
        if (!t.getOwner().equals(myId) && !t.isPublished()) return ResponseEntity.status(403).build();

        Set<UUID> subscribedIds = subscriptionRepository.findByUserId(myId)
                .stream().map(TemplateSubscriptionEntity::getTemplateId).collect(Collectors.toSet());
        return ResponseEntity.ok(toPublicDto(t, myId, subscribedIds));
    }

    // ── Eigene Vorlagen (für Auswahl-Dropdown) ───────────────────────────────

    @GetMapping("/mine")
    public ResponseEntity<List<Map<String, Object>>> getMyTemplates(Principal principal) {
        UUID myId = userService.requireUser(principal).getUserId();

        List<Map<String, Object>> result = templateRepository.findByOwner(myId).stream()
                .map(t -> toPublicDto(t, myId, Set.of()))
                .toList();
        return ResponseEntity.ok(result);
    }

    // ── Abonnierte Vorlagen ───────────────────────────────────────────────────

    @GetMapping("/subscribed")
    public ResponseEntity<List<Map<String, Object>>> getSubscribedTemplates(Principal principal) {
        UUID myId = userService.requireUser(principal).getUserId();

        List<TemplateSubscriptionEntity> subs = subscriptionRepository.findByUserId(myId);
        Set<UUID> subscribedIds = subs.stream().map(TemplateSubscriptionEntity::getTemplateId).collect(Collectors.toSet());

        List<Map<String, Object>> result = new ArrayList<>();
        for (var sub : subs) {
            templateRepository.findById(sub.getTemplateId()).ifPresent(t -> {
                if (t.isPublished()) {
                    result.add(toPublicDto(t, myId, subscribedIds));
                }
            });
        }
        return ResponseEntity.ok(result);
    }

    // ── Abonnieren ────────────────────────────────────────────────────────────

    @PostMapping("/{id}/subscribe")
    @Transactional
    public ResponseEntity<Void> subscribe(@PathVariable("id") UUID id, Principal principal) {
        UUID myId = userService.requireUser(principal).getUserId();

        var tOpt = templateRepository.findById(id);
        if (tOpt.isEmpty() || !tOpt.get().isPublished()) return ResponseEntity.notFound().build();
        if (tOpt.get().getOwner().equals(myId)) return ResponseEntity.status(409).build();
        if (subscriptionRepository.findByUserIdAndTemplateId(myId, id).isPresent())
            return ResponseEntity.status(409).build();

        var sub = new TemplateSubscriptionEntity();
        sub.setUserId(myId);
        sub.setTemplateId(id);
        sub.setSubscribedAt(LocalDateTime.now());
        subscriptionRepository.save(sub);

        var t = tOpt.get();
        t.setSubscriberCount(t.getSubscriberCount() + 1);
        templateRepository.save(t);

        return ResponseEntity.noContent().build();
    }

    // ── Abo kündigen ──────────────────────────────────────────────────────────

    @DeleteMapping("/{id}/subscribe")
    @Transactional
    public ResponseEntity<Void> unsubscribe(@PathVariable("id") UUID id, Principal principal) {
        UUID myId = userService.requireUser(principal).getUserId();

        var subOpt = subscriptionRepository.findByUserIdAndTemplateId(myId, id);
        if (subOpt.isEmpty()) return ResponseEntity.noContent().build();

        subscriptionRepository.delete(subOpt.get());

        templateRepository.findById(id).ifPresent(t -> {
            t.setSubscriberCount(Math.max(0, t.getSubscriberCount() - 1));
            templateRepository.save(t);
        });

        return ResponseEntity.noContent().build();
    }

    // ── Kopie erstellen (Fork) ────────────────────────────────────────────────

    @PostMapping("/{id}/fork")
    @Transactional
    public ResponseEntity<Map<String, Object>> fork(@PathVariable("id") UUID id, Principal principal) {
        UUID myId = userService.requireUser(principal).getUserId();

        var tOpt = templateRepository.findById(id);
        if (tOpt.isEmpty() || !tOpt.get().isPublished()) return ResponseEntity.notFound().build();

        var source = tOpt.get();
        String copyName = (source.getName() != null ? source.getName() : "Vorlage") + " (Kopie)";

        UUID newId;
        if (source instanceof TimeLockTemplateEntity tl) {
            var copy = new TimeLockTemplateEntity();
            copy.setOwner(myId);
            copy.setName(copyName);
            copy.setMinTimeInMinutes(tl.getMinTimeInMinutes());
            copy.setMaxTimeInMinutes(tl.getMaxTimeInMinutes());
            copy.setEndTimeVisible(tl.isEndTimeVisible());
            copy.setHygineOpeningDurationMinutes(tl.getHygineOpeningDurationMinutes());
            copy.setHygineOpeningEveryMinites(tl.getHygineOpeningEveryMinites());
            copy.setTasks(tl.getTasks());
            copy.setTaskEveryMinutes(tl.getTaskEveryMinutes());
            copy.setMinTasksPerDay(tl.getMinTasksPerDay());
            copy.setSpinningWheelEntries(tl.getSpinningWheelEntries());
            copy.setSpinsEveryMinutes(tl.getSpinsEveryMinutes());
            copy.setMinSpinsPerDay(tl.getMinSpinsPerDay());
            copy.setRequiresVerification(tl.isRequiresVerification());
            copy.setTaskMode(tl.getTaskMode() != null ? tl.getTaskMode() : TaskMode.RANDOM);
            copy.setPenaltyType(tl.getPenaltyType());
            copy.setPenaltyValue(tl.getPenaltyValue());
            newId = timeLockTemplateRepository.save(copy).getTemplateId();
        } else if (source instanceof CardlockTemplateEntity cl) {
            var copy = new CardlockTemplateEntity();
            copy.setOwner(myId);
            copy.setName(copyName);
            copy.setCardCountsMin(cl.getCardCountsMin());
            copy.setCardCountsMax(cl.getCardCountsMax());
            copy.setPickEveryMinute(cl.getPickEveryMinute());
            copy.setAccumulatePicks(cl.isAccumulatePicks());
            copy.setShowRemainingCards(cl.isShowRemainingCards());
            copy.setHygineOpeningDurationMinutes(cl.getHygineOpeningDurationMinutes());
            copy.setHygineOpeningEveryMinites(cl.getHygineOpeningEveryMinites());
            copy.setTasks(cl.getTasks());
            copy.setRequiresVerification(cl.isRequiresVerification());
            copy.setTaskMode(cl.getTaskMode() != null ? cl.getTaskMode() : TaskMode.RANDOM);
            newId = cardlockTemplateRepository.save(copy).getTemplateId();
        } else {
            return ResponseEntity.status(500).build();
        }

        return ResponseEntity.ok(Map.of("templateId", newId.toString()));
    }

    // ── Veröffentlichen ───────────────────────────────────────────────────────

    @PatchMapping("/{id}/publish")
    @Transactional
    public ResponseEntity<Void> publish(@PathVariable("id") UUID id, Principal principal) {
        var me = userService.requireUser(principal);

        var tOpt = templateRepository.findById(id);
        if (tOpt.isEmpty()) return ResponseEntity.notFound().build();
        var t = tOpt.get();
        if (!t.getOwner().equals(me.getUserId())) return ResponseEntity.status(403).build();

        t.setPublished(true);
        t.setShowAuthor(me.isProfilBeiVeroeffentlichungenSichtbar());
        templateRepository.save(t);
        return ResponseEntity.noContent().build();
    }

    // ── Veröffentlichung entfernen ────────────────────────────────────────────

    @DeleteMapping("/{id}/publish")
    @Transactional
    public ResponseEntity<Void> unpublish(@PathVariable("id") UUID id, Principal principal) {
        UUID myId = userService.requireUser(principal).getUserId();

        var tOpt = templateRepository.findById(id);
        if (tOpt.isEmpty()) return ResponseEntity.notFound().build();
        var t = tOpt.get();
        if (!t.getOwner().equals(myId)) return ResponseEntity.status(403).build();

        t.setPublished(false);
        t.setShowAuthor(false);
        t.setSubscriberCount(0);
        templateRepository.save(t);
        subscriptionRepository.deleteByTemplateId(id);
        return ResponseEntity.noContent().build();
    }

    // ── DTO Helper ────────────────────────────────────────────────────────────

    private Map<String, Object> toPublicDto(BaseLockTemplateEntity t, UUID myId, Set<UUID> subscribedIds) {
        boolean isOwn = t.getOwner().equals(myId);
        boolean isSubscribed = subscribedIds.contains(t.getTemplateId());

        String authorName = null;
        String authorProfilePicture = null;
        if (t.isShowAuthor()) {
            var authorOpt = userRepository.findById(t.getOwner());
            authorName           = authorOpt.map(u -> u.getName()).orElse(null);
            authorProfilePicture = authorOpt.map(u -> u.getProfilePicture()).orElse(null);
        }

        Map<String, Object> dto = new LinkedHashMap<>();
        dto.put("templateId",       t.getTemplateId().toString());
        dto.put("lockType",         t instanceof TimeLockTemplateEntity ? "TIMELOCK" : "CARDLOCK");
        dto.put("name",             t.getName() != null ? t.getName() : "");
        dto.put("subscriberCount",  t.getSubscriberCount());
        dto.put("authorName",           authorName);
        dto.put("authorProfilePicture", authorProfilePicture);
        dto.put("isOwnTemplate",    isOwn);
        dto.put("isSubscribed",     isSubscribed);
        dto.put("taskCount",        t.getTasks() != null ? t.getTasks().size() : 0);
        dto.put("requiresVerification", t.isRequiresVerification());
        dto.put("hygieneEnabled",   t.getHygineOpeningEveryMinites() != null);
        dto.put("hygineOpeningEveryMinites",    t.getHygineOpeningEveryMinites());
        dto.put("hygineOpeningDurationMinutes", t.getHygineOpeningDurationMinutes());
        dto.put("tasks",            t.getTasks() != null ? t.getTasks() : List.of());
        dto.put("taskMode",         t.getTaskMode() != null ? t.getTaskMode().name() : "RANDOM");

        if (t instanceof TimeLockTemplateEntity tl) {
            dto.put("minTimeInMinutes",      tl.getMinTimeInMinutes());
            dto.put("maxTimeInMinutes",      tl.getMaxTimeInMinutes());
            dto.put("endTimeVisible",        tl.isEndTimeVisible());
            dto.put("taskEveryMinutes",      tl.getTaskEveryMinutes());
            dto.put("minTasksPerDay",        tl.getMinTasksPerDay());
            dto.put("spinningWheelEntries",  tl.getSpinningWheelEntries() != null ? tl.getSpinningWheelEntries() : List.of());
            dto.put("spinsEveryMinutes",     tl.getSpinsEveryMinutes());
            dto.put("minSpinsPerDay",        tl.getMinSpinsPerDay());
            dto.put("penaltyType",           tl.getPenaltyType() != null ? tl.getPenaltyType().name() : null);
            dto.put("penaltyValue",          tl.getPenaltyValue());
        } else if (t instanceof CardlockTemplateEntity cl) {
            dto.put("cardCountsMin",     cl.getCardCountsMin() != null ? cl.getCardCountsMin() : Map.of());
            dto.put("cardCountsMax",     cl.getCardCountsMax() != null ? cl.getCardCountsMax() : Map.of());
            dto.put("pickEveryMinute",   cl.getPickEveryMinute());
            dto.put("accumulatePicks",   cl.isAccumulatePicks());
            dto.put("showRemainingCards",cl.isShowRemainingCards());
        }

        return dto;
    }
}
