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

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.oaa.xxx.games.chastity.common.BaseLockEntity;
import de.oaa.xxx.games.chastity.common.BaseLockService;
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteRepository;
import de.oaa.xxx.games.chastity.community.CommunityVerificationRepository;
import de.oaa.xxx.games.chastity.community.CommunityVerificationVoteRepository;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceRepository;
import de.oaa.xxx.games.chastity.keyholder.KeyholderVerificationRepository;
import de.oaa.xxx.games.chastity.lockcontroll.LockControlCallback;
import de.oaa.xxx.games.chastity.lockcontroll.LockControlFactory;
import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity;
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
import de.oaa.xxx.games.history.GameHistoryRepository;
import de.oaa.xxx.games.history.GameType;
import de.oaa.xxx.social.SystemMessageService;
import de.oaa.xxx.user.UserRepository;

public class CardLockService extends BaseLockService implements LockControlCallback {

    private static final Logger LOGGER = LoggerFactory.getLogger(CardLockService.class);
    private final CardLockEntity lock;
    private final CardLockRepository cardLockRepository;
    private String pendingTaskMode;
	
    public CardLockService(
            CardLockEntity lock,
            CommunityVerificationVoteRepository communityVerificationVoteRepository,
            CommunityVerificationRepository communityVerificationRepository,
            KeyholderVerificationRepository keyholderVerificationRepository,
            GameHistoryRepository gameHistoryRepository,
            UserRepository userRepository,
            KeyholderNotificationRepository keyholderNotificationRepository,
            SystemMessageService systemMessageService,
            UnlockCodeHistoryService unlockCodeHistoryService,
            KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
            CommunityTaskVoteRepository communityTaskVoteRepository,
            CardLockRepository cardLockRepository,
            LockControlFactory lockControlFactory) {
        super(communityVerificationVoteRepository, communityVerificationRepository, keyholderVerificationRepository,
                gameHistoryRepository, userRepository, keyholderNotificationRepository, systemMessageService,
                unlockCodeHistoryService, keyholderTaskChoiceRepository, communityTaskVoteRepository);
        this.lock = lock;
        this.cardLockRepository = cardLockRepository;
        // lockControl aus Entity-Typ wiederherstellen (für bereits laufende Locks)
        if (lock.getControllType() != null) {
            this.lockControl = lockControlFactory.create(lock.getControllType(), this, lock.getLockee());
        }
    }

    // ── LockControl Setup ─────────────────────────────────────────────────────

    /** Wird von CardLockServiceFactory gesetzt (package-private). */
    void initLockControl(de.oaa.xxx.games.chastity.lockcontroll.LockControl lc) {
        this.lockControl = lc;
    }

    // ── LockControlCallback ───────────────────────────────────────────────────

    @Override
    public void setUnlockCode(String code) {
        lock.setUnlockCode(code);
        cardLockRepository.save(lock);
    }

    @Override
    public int getUnlockcodeLenght() {
        return lock.getUnlockCodeLength() != null ? lock.getUnlockCodeLength() : 5;
    }

    // ── Abstract method implementations ──────────────────────────────────────

    @Override
    protected BaseLockEntity getLock() {
        return lock;
    }

    @Override
    protected void saveLock() {
        cardLockRepository.save(lock);
    }

    @Override
    protected GameType getGameType() {
        return GameType.CARDLOCK;
    }

    @Override
    protected void applyHygieneOvertime(Long overtime) {
    	LOGGER.debug("Apply {} Minutes Overtime");
        if (lock.getFrozenUntil() != null) {
            lock.setFrozenUntil(lock.getFrozenUntil().plusMinutes(overtime * 4));
        } else {
            lock.setFrozenUntil(LocalDateTime.now().plusMinutes(overtime * 4));
        }
        LOGGER.debug("Frozen until {}", lock.getFrozenUntil());
    }

    // ── Card drawing ──────────────────────────────────────────────────────────

    public CardDTO getNextCard() {
        LOGGER.debug("New Card requested by user {}", lock.getLockee());
        CardDTO card = null;
        if (lock.isKeyholderRequestedUnlock()
                || (lock.getLatestOpeningtime() != null && lock.getLatestOpeningtime().isAfter(LocalDateTime.now()))) {
            card = getGreenCard();
        } else if (lock.isAccumulatePicks()) {
            if (lock.getNextCardIn().isBefore(LocalDateTime.now())) {
                lock.setOpenPicks(lock.getOpenPicks() == null ? 1 : lock.getOpenPicks() + 1);
            }
            if (lock.getOpenPicks() != null && lock.getOpenPicks() > 0) {
                lock.setOpenPicks(lock.getOpenPicks() - 1);
                card = getRandomCard();
            }
        } else {
            if (lock.getNextCardIn().isBefore(LocalDateTime.now())) {
                lock.setNextCardIn(LocalDateTime.now().plusMinutes(lock.getPickEveryMinute()));
                card = getRandomCard();
            }
        }
        cardLockRepository.save(lock);
        return card;
    }

    private CardDTO getRandomCard() {
        var cards = lock.getAvailableCards();
        if (!cards.isEmpty()) {
            var card = cards.get(new Random().nextInt(cards.size()));
            LOGGER.debug("Card drafted: {}", card);
            lock.getAvailableCards().remove(card);
            return card.get().processCard(this);
        }
        LOGGER.error("Keine Karten mehr im Lock - generiere Notfall Grüne Karte");
        return getGreenCard();
    }

    private CardDTO getGreenCard() {
        return new CardDTO(CardEnum.GREEN, lock.getUnlockCode());
    }

    // ── Card effects ──────────────────────────────────────────────────────────

    public String doubleUp() {
        var cards = lock.getAvailableCards();
        LOGGER.debug("Double up {} cards", cards.size());
        lock.getAvailableCards().addAll(cards);
        LOGGER.debug("Now {} cards", lock.getAvailableCards().size());
        return "";
    }

    public String reset() {
        LOGGER.debug("Reset to initial cards");
        lock.setAvailableCards(lock.getInitialCards());
        return "";
    }

    public String green() {
        LOGGER.debug("Green Card drafted");
        return lock.getUnlockCode();
    }

    public String freeze() {
        var multiplier = lock.getPickEveryMinute() * new Random().nextDouble(1.0, 4.0);
        freeze(multiplier);
        return "";
    }

    private String freeze(double multiplier) {
        LocalDateTime frozenTill = LocalDateTime.now().plus((long) multiplier, ChronoUnit.MINUTES);
        lock.setFrozenUntil(frozenTill);
        lock.setNextCardIn(frozenTill);
        LOGGER.debug("Frozen until {}", lock.getFrozenUntil());
        return "";
    }

    /** Called by TaskCard. Dispatches based on TaskMode and stores result for controller. */
    public String task() {
        switch (lock.getTaskMode()) {
            case RANDOM -> applyRandomTask();
            case KEYHOLDER -> {
                if (lock.isTestLock()) applyRandomTask();
                else startKeyholderVote();
            }
            case COMMUNITY -> {
                if (lock.isTestLock()) applyRandomTask();
                else startCommunityVote();
            }
        }
        pendingTaskMode = lock.getTaskMode().name();
        return "";
    }

    /** Returns the TaskMode that was triggered by the last task() call, or null if no task card was drawn. */
    public String getPendingTaskMode() {
        return pendingTaskMode;
    }

    public String redCard() {
        return "";
    }

    public String yellowCard() {
        Random random = new Random();
        if (random.nextBoolean()) {
            for (int i = 0; i < random.nextInt(1, 3); i++) {
                LOGGER.debug("Adding Red card");
                lock.getAvailableCards().add(CardEnum.RED);
            }
        } else {
            for (int i = 0; i < random.nextInt(1, 3); i++) {
                LOGGER.debug("Removing Red card if possible");
                lock.getAvailableCards().remove(CardEnum.RED);
            }
        }
        return "";
    }

    public void putBackGreen() {
        LOGGER.debug("Green Card was put Back");
        lock.getAvailableCards().add(CardEnum.GREEN);
        cardLockRepository.save(lock);
    }

    // ── Hygiene opening ───────────────────────────────────────────────────────

    @Override
    protected void afterHygieneClosing() {
        if (lockControl != null) lockControl.lock();
    }

    public void startHygieneOpening() {
        startTempOpening(TempOpeningReason.HYGIENE, lock.getHygineOpeningDurationMinutes());
    }

    // ── Cum cards ─────────────────────────────────────────────────────────────

    public String cum(boolean tempUnlock) {
        if (tempUnlock) {
            startTempOpening(TempOpeningReason.CARD, 0);
        }
        return lock.getUnlockCode();
    }

    // ── Assigned task penalty ─────────────────────────────────────────────────

    public void applyAssignedTaskPenalty(AssignedTaskEntity task) {
        if (task.getPenaltyFreezeMinutes() != null && task.getPenaltyFreezeMinutes() > 0) {
            LocalDateTime until = LocalDateTime.now().plusMinutes(task.getPenaltyFreezeMinutes());
            if (lock.getFrozenUntil() == null || until.isAfter(lock.getFrozenUntil())) {
                lock.setFrozenUntil(until);
                lock.setNextCardIn(until);
            }
        }
        if (task.getPenaltyRedCards() != null && task.getPenaltyRedCards() > 0) {
            List<CardEnum> cards = new ArrayList<>(
                    lock.getAvailableCards() != null ? lock.getAvailableCards() : List.of());
            for (int i = 0; i < task.getPenaltyRedCards(); i++) {
                cards.add(CardEnum.RED);
            }
            lock.setAvailableCards(cards);
        }
        cardLockRepository.save(lock);
    }
}
