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

import java.security.Principal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import de.oaa.xxx.games.chastity.cardlock.CardEnum;
import de.oaa.xxx.games.chastity.cardlock.CardLockEntity;
import de.oaa.xxx.games.chastity.cardlock.CardLockService;
import de.oaa.xxx.games.chastity.cardlock.CardLockServiceFactory;
import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
import de.oaa.xxx.games.chastity.cardlock.CardlockTemplateEntity;
import de.oaa.xxx.games.chastity.common.BaseLockTemplateRepository;
import de.oaa.xxx.games.chastity.common.CodeCreator;
import de.oaa.xxx.games.chastity.common.TemplateSubscriptionRepository;
import de.oaa.xxx.games.chastity.lockcontroll.LockControllType;
import de.oaa.xxx.games.chastity.tasks.TaskMode;
import de.oaa.xxx.games.chastity.timelock.TimeLockAdditionalSettings;
import de.oaa.xxx.games.chastity.timelock.TimeLockEntity;
import de.oaa.xxx.games.chastity.timelock.TimeLockRepository;
import de.oaa.xxx.games.chastity.timelock.TimeLockServiceFactory;
import de.oaa.xxx.games.chastity.timelock.TimeLockTemplateEntity;
import de.oaa.xxx.social.SystemMessageService;
import de.oaa.xxx.social.entity.MessageCause;
import de.oaa.xxx.subscription.SubscriptionLimitService;
import de.oaa.xxx.user.UserRepository;
import de.oaa.xxx.user.UserService;

@RestController
@RequestMapping("/keyholder-offers")
public class KeyholderOfferController {

    private final KeyholderOfferRepository offerRepository;
    private final BaseLockTemplateRepository templateRepository;
    private final TemplateSubscriptionRepository subscriptionRepository;
    private final UserRepository userRepository;
    private final CardlockRepository cardlockRepository;
    private final TimeLockRepository timeLockRepository;
    private final CardLockServiceFactory cardLockServiceFactory;
    private final TimeLockServiceFactory timeLockServiceFactory;
    private final KeyholderInvitationRepository invitationRepository;
    private final SystemMessageService systemMessageService;
    private final SubscriptionLimitService subscriptionLimitService;
    private final UserService userService;

    public KeyholderOfferController(
            KeyholderOfferRepository offerRepository,
            BaseLockTemplateRepository templateRepository,
            TemplateSubscriptionRepository subscriptionRepository,
            UserRepository userRepository,
            CardlockRepository cardlockRepository,
            TimeLockRepository timeLockRepository,
            CardLockServiceFactory cardLockServiceFactory,
            TimeLockServiceFactory timeLockServiceFactory,
            KeyholderInvitationRepository invitationRepository,
            SystemMessageService systemMessageService,
            SubscriptionLimitService subscriptionLimitService,
            UserService userService) {
        this.offerRepository         = offerRepository;
        this.templateRepository      = templateRepository;
        this.subscriptionRepository  = subscriptionRepository;
        this.userRepository          = userRepository;
        this.cardlockRepository      = cardlockRepository;
        this.timeLockRepository      = timeLockRepository;
        this.cardLockServiceFactory  = cardLockServiceFactory;
        this.timeLockServiceFactory  = timeLockServiceFactory;
        this.invitationRepository    = invitationRepository;
        this.systemMessageService    = systemMessageService;
        this.subscriptionLimitService = subscriptionLimitService;
        this.userService             = userService;
    }

    // ── Eigene Angebote ───────────────────────────────────────────────────────

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

        List<Map<String, Object>> result = offerRepository.findByOffererId(myId).stream()
                .map(this::toDto)
                .toList();
        return ResponseEntity.ok(result);
    }

    record CreateOfferRequest(UUID templateId, List<String> targetGenders, boolean directStart) {}

    @PostMapping
    @Transactional
    public ResponseEntity<Map<String, Object>> createOffer(
            @RequestBody CreateOfferRequest req, Principal principal) {
        var me = userService.requireUser(principal);
        UUID myId = me.getUserId();

        // Limit prüfen
        long existing = offerRepository.countByOffererId(myId);
        if (existing >= subscriptionLimitService.maxKeyholderOffers(myId)) {
            return ResponseEntity.status(403).body(Map.of("error", "offer_limit_reached"));
        }

        // Template muss dem User gehören oder abonniert sein
        var tOpt = templateRepository.findById(req.templateId());
        if (tOpt.isEmpty()) return ResponseEntity.badRequest().build();
        var t = tOpt.get();

        boolean isOwn       = t.getOwner().equals(myId);
        boolean isSubscribed = subscriptionRepository.findByUserIdAndTemplateId(myId, req.templateId()).isPresent();
        if (!isOwn && !isSubscribed) return ResponseEntity.status(403).build();

        String genders = req.targetGenders() != null
                ? String.join(",", req.targetGenders())
                : "";

        String templateType = t instanceof TimeLockTemplateEntity ? "TIMELOCK" : "CARDLOCK";

        KeyholderOfferEntity offer = new KeyholderOfferEntity();
        offer.setOffererId(myId);
        offer.setTemplateId(req.templateId());
        offer.setTemplateName(t.getName() != null ? t.getName() : "Unbenannt");
        offer.setTemplateType(templateType);
        offer.setTargetGenders(genders);
        offer.setDirectStart(req.directStart());
        offer.setAcceptanceCount(0);
        offer.setCreatedAt(LocalDateTime.now());
        offerRepository.save(offer);

        return ResponseEntity.ok(toDto(offer));
    }

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

        var offerOpt = offerRepository.findById(id);
        if (offerOpt.isEmpty()) return ResponseEntity.notFound().build();
        var offer = offerOpt.get();
        if (!offer.getOffererId().equals(myId)) return ResponseEntity.status(403).build();

        // Template darf geändert werden – muss dem User gehören oder abonniert sein
        var tOpt = templateRepository.findById(req.templateId());
        if (tOpt.isEmpty()) return ResponseEntity.badRequest().build();
        var t = tOpt.get();
        boolean isOwn       = t.getOwner().equals(myId);
        boolean isSubscribed = subscriptionRepository.findByUserIdAndTemplateId(myId, req.templateId()).isPresent();
        if (!isOwn && !isSubscribed) return ResponseEntity.status(403).build();

        String templateType = t instanceof TimeLockTemplateEntity ? "TIMELOCK" : "CARDLOCK";
        String genders = req.targetGenders() != null ? String.join(",", req.targetGenders()) : "";

        offer.setTemplateId(req.templateId());
        offer.setTemplateName(t.getName() != null ? t.getName() : "Unbenannt");
        offer.setTemplateType(templateType);
        offer.setTargetGenders(genders);
        offer.setDirectStart(req.directStart());
        offerRepository.save(offer);

        return ResponseEntity.ok(toDto(offer));
    }

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

        var offerOpt = offerRepository.findById(id);
        if (offerOpt.isEmpty()) return ResponseEntity.notFound().build();
        if (!offerOpt.get().getOffererId().equals(myId)) return ResponseEntity.status(403).build();

        offerRepository.delete(offerOpt.get());
        return ResponseEntity.noContent().build();
    }

    // ── Angebote eines bestimmten Nutzers (für Profilseite) ──────────────────

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

        List<Map<String, Object>> result = offerRepository.findByOffererId(userId).stream()
                .map(o -> toPublicDto(o, myId))
                .toList();
        return ResponseEntity.ok(result);
    }

    // ── Öffentliche Angebotsübersicht ─────────────────────────────────────────

    @GetMapping("/public")
    public ResponseEntity<List<Map<String, Object>>> getPublicOffers(Principal principal) {
        var me = userService.requireUser(principal);

        String userGender = me.getGeschlecht() != null ? me.getGeschlecht().name() : null;

        List<Map<String, Object>> result = offerRepository.findAllByOrderByAcceptanceCountDesc().stream()
                .filter(o -> matchesGender(o, userGender))
                .map(o -> toPublicDto(o, me.getUserId()))
                .toList();

        return ResponseEntity.ok(result);
    }

    private boolean matchesGender(KeyholderOfferEntity o, String userGender) {
        String tg = o.getTargetGenders();
        if (tg == null || tg.isBlank()) return true; // alle Geschlechter
        if (userGender == null) return false;          // User ohne Geschlecht: nur unrestricted
        return Arrays.asList(tg.split(",")).contains(userGender);
    }

    // ── Angebot annehmen (Lock erstellen) ─────────────────────────────────────

    record JoinOfferRequest(LockControllType controllType, Integer unlockCodeLength) {}

    @PostMapping("/{id}/join")
    @Transactional
    public ResponseEntity<Map<String, Object>> joinOffer(
            @PathVariable UUID id,
            @RequestBody JoinOfferRequest req,
            Principal principal) {

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

        var offerOpt = offerRepository.findById(id);
        if (offerOpt.isEmpty()) return ResponseEntity.notFound().build();
        var offer = offerOpt.get();

        if (offer.getOffererId().equals(myId))
            return ResponseEntity.status(409).body(Map.of("error", "own_offer"));

        // Aktives Lock prüfen
        if (cardLockServiceFactory.hasActiveLock(myId))
            return ResponseEntity.status(409).body(Map.of("error", "active_lock_exists"));

        var tOpt = templateRepository.findById(offer.getTemplateId());
        if (tOpt.isEmpty()) return ResponseEntity.status(410).body(Map.of("error", "template_gone"));
        var template = tOpt.get();

        var offererOpt = userRepository.findById(offer.getOffererId());
        if (offererOpt.isEmpty()) return ResponseEntity.status(410).body(Map.of("error", "offerer_gone"));
        var offerer = offererOpt.get();

        LockControllType controllType = req.controllType() != null ? req.controllType() : LockControllType.UNLOCK_CODE;
        int codeLen = (req.unlockCodeLength() != null && req.unlockCodeLength() >= 1) ? req.unlockCodeLength() : 5;
        boolean directStart = offer.isDirectStart();
        UUID keyholderIdIfDirect = directStart ? offer.getOffererId() : null;

        UUID lockId;
        String unlockCode = null;

        if (template instanceof TimeLockTemplateEntity tl) {
            TimeLockAdditionalSettings settings = new TimeLockAdditionalSettings(
                    controllType, myId, keyholderIdIfDirect, false, codeLen);
            TimeLockEntity lock = new TimeLockEntity();
            timeLockServiceFactory.create(lock).init(tl, settings);
            timeLockRepository.save(lock);
            lockId    = lock.getLockId();
            unlockCode = lock.getUnlockCode();

        } else if (template instanceof CardlockTemplateEntity cl) {
            List<CardEnum> cards = buildCardList(cl);
            if (cards.isEmpty()) return ResponseEntity.badRequest().body(Map.of("error", "empty_deck"));

            CardLockEntity lock = new CardLockEntity();
            lock.setName(template.getName());
            lock.setLockee(myId);
            lock.setKeyholder(keyholderIdIfDirect);
            lock.setInitialCards(cards);
            lock.setPickEveryMinute(cl.getPickEveryMinute() != null ? cl.getPickEveryMinute() : 60);
            lock.setAccumulatePicks(cl.isAccumulatePicks());
            lock.setShowRemainingCards(cl.isShowRemainingCards());
            lock.setHygineOpeningDurationMinutes(cl.getHygineOpeningDurationMinutes());
            lock.setHygineOpeningEveryMinites(cl.getHygineOpeningEveryMinites());
            lock.setTasks(cl.getTasks() != null ? cl.getTasks() : List.of());
            lock.setRequiresVerification(cl.isRequiresVerification());
            lock.setTestLock(false);
            lock.setTaskMode(cl.getTaskMode() != null ? cl.getTaskMode() : TaskMode.RANDOM);
            lock.setUnlockCodeLength(codeLen);
            lock.setControllType(controllType);

            LocalDateTime now = LocalDateTime.now();
            lock.setStartTime(now);
            lock.setAvailableCards(new ArrayList<>(cards));
            lock.setOpenPicks(0);
            lock.setNextCardIn(now.plusMinutes(lock.getPickEveryMinute()));
            if (cl.getHygineOpeningEveryMinites() != null) {
                lock.setLastHygineOpening(now);
            }
            cardlockRepository.save(lock);

            CardLockService initService = cardLockServiceFactory.create(lock);
            if (initService.getLockControl() != null) {
                initService.getLockControl().lock();
            } else {
                lock.setUnlockCode(CodeCreator.createNumeric(codeLen));
                cardlockRepository.save(lock);
            }
            lockId    = lock.getLockId();
            unlockCode = lock.getUnlockCode();
        } else {
            return ResponseEntity.status(500).build();
        }

        String lockName = template.getName() != null ? template.getName() : "Unbenanntes Lock";
        boolean invitationSent = false;
        if (!directStart) {
            // Normaler Einladungsworkflow: Keyholder muss bestätigen
            KeyholderInvitationEntity inv = new KeyholderInvitationEntity();
            inv.setLockId(lockId);
            inv.setKeyholderUserId(offer.getOffererId());
            inv.setLockeeUserId(myId);
            inv.setToken(UUID.randomUUID().toString().replace("-", ""));
            inv.setCreatedAt(LocalDateTime.now());
            invitationRepository.save(inv);

            systemMessageService.send(myId, offerer.getUserId(),
                    me.getName() + " möchte dein Keyholder-Angebot annehmen und lädt dich als Keyholder für „"
                            + lockName + "\" ein.",
                    "/community/einladungen.html", MessageCause.INVITATION);
            invitationSent = true;
        } else {
            // Direktstart: Keyholder wird direkt gesetzt, aber trotzdem benachrichtigen
            systemMessageService.send(myId, offerer.getUserId(),
                    me.getName() + " hat dein Keyholder-Angebot angenommen und das Lock „"
                            + lockName + "\" gestartet.",
                    "/games/chastity/keyholder.html", MessageCause.INVITATION);
        }

        // Annahmezähler erhöhen
        offer.setAcceptanceCount(offer.getAcceptanceCount() + 1);
        offerRepository.save(offer);

        Map<String, Object> response = new LinkedHashMap<>();
        response.put("lockId", lockId.toString());
        response.put("invitationSent", invitationSent);
        if (unlockCode != null) response.put("unlockCode", unlockCode);
        return ResponseEntity.ok(response);
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private List<CardEnum> buildCardList(CardlockTemplateEntity cl) {
        List<CardEnum> cards = new ArrayList<>();
        Map<String, Integer> counts = cl.getCardCountsMax();
        if (counts == null) return cards;
        counts.forEach((type, count) -> {
            if (count != null && count > 0) {
                try {
                    CardEnum card = CardEnum.valueOf(type);
                    for (int i = 0; i < count; i++) cards.add(card);
                } catch (IllegalArgumentException ignored) {}
            }
        });
        return cards;
    }

    private Map<String, Object> toDto(KeyholderOfferEntity o) {
        Map<String, Object> dto = new LinkedHashMap<>();
        dto.put("id",              o.getId().toString());
        dto.put("templateId",      o.getTemplateId().toString());
        dto.put("templateName",    o.getTemplateName());
        dto.put("templateType",    o.getTemplateType());
        dto.put("targetGenders",   o.getTargetGenders() != null ? Arrays.asList(o.getTargetGenders().split(",")).stream().filter(s -> !s.isBlank()).toList() : List.of());
        dto.put("directStart",     o.isDirectStart());
        dto.put("acceptanceCount", o.getAcceptanceCount());
        dto.put("createdAt",       o.getCreatedAt().toString());
        return dto;
    }

    private Map<String, Object> toPublicDto(KeyholderOfferEntity o, UUID myId) {
        Map<String, Object> dto = toDto(o);
        dto.put("isOwn", o.getOffererId().equals(myId));
        userRepository.findById(o.getOffererId()).ifPresent(u -> {
            dto.put("offererName",       u.getName());
            dto.put("offererProfilePic", u.getProfilePicture());
        });
        return dto;
    }
}
