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

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Random;
import java.util.UUID;

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.common.CodeCreator;
import de.oaa.xxx.games.chastity.common.VerificationCommonDTO;
import de.oaa.xxx.games.chastity.community.CommunityPilloryEntity;
import de.oaa.xxx.games.chastity.community.CommunityPilloryReason;
import de.oaa.xxx.games.chastity.community.CommunityPilloryRepository;
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteRepository;
import de.oaa.xxx.games.chastity.community.CommunityVerificationEntity;
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.KeyholderVerificationEntity;
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.spinningwheel.SpinningWheelEntry;
import de.oaa.xxx.games.chastity.tasks.TaskMode;
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 TimeLockService extends BaseLockService implements LockControlCallback {

    private static final Logger LOGGER = LoggerFactory.getLogger(TimeLockService.class);
    private final TimeLockEntity lock;
    private final TimeLockRepository timeLockRepository;
    private final CommunityPilloryRepository pilloryRepository;
    private final LockControlFactory lockControlFactory;

    // lockControl ist in BaseLockService als protected-Feld definiert

    public TimeLockService(TimeLockEntity lock,
            CommunityVerificationRepository verificationRepository,
            CommunityVerificationVoteRepository verificationVoteRepository,
            TimeLockRepository timeLockRepository,
            GameHistoryRepository gameHistoryRepository,
            UserRepository userRepository,
            KeyholderNotificationRepository keyholderNotificationRepository,
            KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
            KeyholderVerificationRepository keyholderVerificationRepository,
            CommunityTaskVoteRepository communityTaskVoteRepository,
            CommunityPilloryRepository pilloryRepository,
            UnlockCodeHistoryService unlockCodeHistoryService,
            SystemMessageService systemMessageService,
            LockControlFactory lockControlFactory) {
        super(verificationVoteRepository, verificationRepository, keyholderVerificationRepository,
                gameHistoryRepository, userRepository, keyholderNotificationRepository,
                systemMessageService, unlockCodeHistoryService,
                keyholderTaskChoiceRepository, communityTaskVoteRepository);
        this.lock = lock;
        this.timeLockRepository = timeLockRepository;
        this.pilloryRepository = pilloryRepository;
        this.lockControlFactory = lockControlFactory;
        // lockControl aus Entity-Typ wiederherstellen (für bereits laufende Locks)
        if (lock.getControllType() != null) {
            this.lockControl = lockControlFactory.create(lock.getControllType(), this, lock.getLockee());
        }
    }

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

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

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

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

    @Override
    protected void applyHygieneOvertime(Long overtime) {
    	LOGGER.debug("Apply {} Minutes Overtime");
        lock.setEstimatedUnlockTime(lock.getEstimatedUnlockTime().plusMinutes(overtime * 4));
        LOGGER.debug("New estimated endtime {}", lock.getEstimatedUnlockTime());
    }

    // ── Hook overrides ────────────────────────────────────────────────────────

    @Override
    protected void beforePhysicalUnlock() {
        lockControl.unlock();
    }

    @Override
    protected void afterHygieneClosing() {
        lockControl.lock();
    }

    // ── Initialisation ────────────────────────────────────────────────────────

    /**
     * Initialisiert ein neues Lock anhand eines Template und Laufzeit-Einstellungen.
     * Ruft am Ende lockControl.lock() auf – bei UNLOCK_CODE wird dabei der Entsperrcode
     * generiert und das Lock bereits persistiert.
     */
    public void init(TimeLockTemplateEntity template, TimeLockAdditionalSettings settings) {
        de.oaa.xxx.games.chastity.lockcontroll.LockControllType type =
                settings.controllType() != null ? settings.controllType()
                        : de.oaa.xxx.games.chastity.lockcontroll.LockControllType.UNLOCK_CODE;
        lock.setControllType(type);
        lockControl = lockControlFactory.create(type, this, settings.lockee());

        LocalDateTime now = LocalDateTime.now();
        lock.setStartTime(now);
        lock.setName(template.getName());
        lock.setLockee(settings.lockee());
        lock.setKeyholder(settings.keyholder());
        lock.setRequiresVerification(template.isRequiresVerification());
        lock.setTestLock(settings.testlock());
        lock.setUnlockCodeLength(settings.unlockCodeLength() != null ? settings.unlockCodeLength() : 5);

        Integer minMinutes = template.getMinTimeInMinutes();
        Integer maxMinutes = template.getMaxTimeInMinutes() != null ? template.getMaxTimeInMinutes() : 60;
        int unlockTimeMinutes = (minMinutes != null && minMinutes < maxMinutes)
                ? minMinutes + new Random().nextInt(maxMinutes - minMinutes)
                : maxMinutes;
        lock.setEstimatedUnlockTime(now.plusMinutes(unlockTimeMinutes));
        lock.setEndTimeVisible(template.isEndTimeVisible());

        lock.setTasks(template.getTasks());
        lock.setTaskEveryMinutes(template.getTaskEveryMinutes());
        lock.setMinTasksPerDay(template.getMinTasksPerDay());

        lock.setSpinningWheelEntries(template.getSpinningWheelEntries());
        lock.setSpinsEveryMinutes(template.getSpinsEveryMinutes());
        lock.setMinSpinsPerDay(template.getMinSpinsPerDay());

        lock.setHygineOpeningDurationMinutes(template.getHygineOpeningDurationMinutes());
        lock.setHygineOpeningEveryMinites(template.getHygineOpeningEveryMinites());
        if (template.getHygineOpeningEveryMinites() != null) {
            lock.setLastHygineOpening(now);
        }

        lock.setTaskMode(template.getTaskCardMode());
        lock.setPenaltyType(template.getPenaltyType());
        lock.setPenaltyValue(template.getPenaltyValue());
        lock.setMinTimeInMinutes(template.getMinTimeInMinutes());
        lock.setMaxTimeInMinutes(template.getMaxTimeInMinutes());

        lockControl.lock();
    }

    // ── Spinning wheel ────────────────────────────────────────────────────────

    public SpinningWheelEntry spinWheel() {
        if (TempOpeningReason.HYGIENE != lock.getTempOpeningReason()) {
            var entries = lock.getSpinningWheelEntries();
            var entry = entries.get(new Random().nextInt(entries.size()));
            entry.getType().apply(this, entry.getIntVal(), entry.getStringVal());
            return entry;
        }
        // Nicht während der Hygiene-Öffnung
        return null;
    }

    // ── Time controls ─────────────────────────────────────────────────────────

    public void addTime(Integer intVal) {
        LOGGER.debug("Lock addTime: %s minutes", intVal);
        lock.setEstimatedUnlockTime(lock.getEstimatedUnlockTime().plusMinutes(intVal));
    }

    public void removeTime(Integer intVal) {
        LOGGER.debug("Lock removeTime: %s minutes", intVal);
        lock.setEstimatedUnlockTime(lock.getEstimatedUnlockTime().minusMinutes(intVal));
    }

    public void freeze(Integer intVal) {
        LOGGER.debug("Lock frozen for %s minutes", intVal);
        lock.setFrozenFrom(LocalDateTime.now());
        lock.setFrozenUntil(LocalDateTime.now().plusMinutes(intVal));
    }

    public void freeze() {
        LOGGER.debug("Lock frozen");
        lock.setFrozenFrom(LocalDateTime.now());
    }

    public void unfreeze() {
        if (lock.getFrozenFrom() != null) {
            var unfreeTime = lock.getFrozenUntil() != null ? lock.getFrozenUntil() : LocalDateTime.now();
            var diff = ChronoUnit.MINUTES.between(lock.getFrozenFrom(), unfreeTime);
            LOGGER.debug("Lock unfrozen - adding %s minutes to the lock", diff);
            lock.setEstimatedUnlockTime(lock.getEstimatedUnlockTime().plusMinutes(diff));
        } else {
            LOGGER.debug("Lock not frozen - ignore Call");
        }
    }

    public void testUnfreeze() {
        if (lock.getFrozenUntil().isAfter(LocalDateTime.now())) {
            unfreeze();
        }
    }

    // ── Tasks ─────────────────────────────────────────────────────────────────

    public void task() {
        if (TempOpeningReason.HYGIENE != lock.getTempOpeningReason()) {
            switch (lock.getTaskMode()) {
            case TaskMode.RANDOM -> applyRandomTask();
            case TaskMode.KEYHOLDER -> startKeyholderVote();
            case TaskMode.COMMUNITY -> {
                    if (lock.isTestLock()) applyRandomTask();
                    else startCommunityVote();
                }
            }
        }
        // Nicht während der Hygiene-Öffnung
    }

    public void text(Integer intVal, String stringVal) {
        LOGGER.debug("Apply text {}", stringVal);
        lock.setCurrentTask(stringVal);
        if (intVal != null && intVal > 0) {
            lock.setTaskUntil(LocalDateTime.now().plusMinutes(intVal));
        }
    }

    // ── Verification ──────────────────────────────────────────────────────────

    /**
     * Gibt eine bestehende Verifikation für heute zurück (Idempotenz) oder legt eine neue an.
     * Erstellt je nach Keyholder-Präsenz eine KeyholderVerification oder CommunityVerification.
     */
    public VerificationCommonDTO startVerification() {
        LocalDate today = LocalDate.now();
        LocalDateTime todayStart = today.atStartOfDay();
        LocalDateTime todayEnd = todayStart.plusDays(1);

        if (lock.getKeyholder() != null) {
            var existing = keyholderVerificationRepository.findByLockId(lock.getLockId()).stream()
                    .filter(v -> v.getCreatedAt().toLocalDate().equals(today))
                    .findFirst();
            if (existing.isPresent()) return existing.get().toCommonVerification();

            KeyholderVerificationEntity v = new KeyholderVerificationEntity();
            v.setId(UUID.randomUUID());
            v.setLockId(lock.getLockId());
            v.setLockeeId(lock.getLockee());
            v.setKeyholderId(lock.getKeyholder());
            v.setCode(CodeCreator.createAlphanumeric(6));
            v.setCreatedAt(LocalDateTime.now());
            keyholderVerificationRepository.save(v);
            return v.toCommonVerification();
        } else {
            var pending = communityVerificationRepository
                    .findByLockIdAndCreatedAtBetweenAndImageIsNull(lock.getLockId(), todayStart, todayEnd);
            if (!pending.isEmpty()) return pending.get(0).toCommonVerification();
            var completed = communityVerificationRepository
                    .findByLockIdAndCreatedAtBetweenAndImageIsNotNull(lock.getLockId(), todayStart, todayEnd);
            if (!completed.isEmpty()) return completed.get(0).toCommonVerification();

            CommunityVerificationEntity v = new CommunityVerificationEntity();
            v.setId(UUID.randomUUID());
            v.setLockId(lock.getLockId());
            v.setLockeeId(lock.getLockee());
            v.setCode(CodeCreator.createAlphanumeric(6));
            v.setCreatedAt(LocalDateTime.now());
            communityVerificationRepository.save(v);
            return v.toCommonVerification();
        }
    }

    /**
     * Speichert das Verifikationsbild auf der richtigen Entity (Keyholder oder Community).
     * Gibt false zurück wenn die Verifikation nicht gefunden wurde oder nicht zu diesem Lock gehört.
     */
    public boolean completeVerification(UUID verificationId, byte[] image) {
        if (lock.getKeyholder() != null) {
            var vOpt = keyholderVerificationRepository.findById(verificationId);
            if (vOpt.isEmpty() || !vOpt.get().getLockId().equals(lock.getLockId())) return false;
            var v = vOpt.get();
            v.setImage(image);
            keyholderVerificationRepository.save(v);
        } else {
            var vOpt = communityVerificationRepository.findById(verificationId);
            if (vOpt.isEmpty() || !vOpt.get().getLockId().equals(lock.getLockId())) return false;
            var v = vOpt.get();
            v.setImage(image);
            communityVerificationRepository.save(v);
        }
        return true;
    }

    // ── Penalty & check ───────────────────────────────────────────────────────

    public void applyPenalty() {
        if (lock.getPenaltyType() != null) {
            switch (lock.getPenaltyType()) {
            case ADD -> addTime(lock.getPenaltyValue());
            case FREEZE -> freeze();
            case PILLORY -> pillory(CommunityPilloryReason.HYGIENE_OPENING_EXEEDED, null);
            }
        }
    }

    public void check() {
        if (lock.getStartTime() == null) return;
        LocalDate today = LocalDate.now();
        if (!lock.getStartTime().toLocalDate().equals(today)) {
            if (lock.getLastCheck() != null || today.isAfter(lock.getLastCheck())) {
                LOGGER.info("Check the day before for violations");
                LocalDate yesterday = today.minusDays(1);
                boolean violation = false;
                if (lock.getMinTasksPerDay() != null) {
                    if (lock.getMinTasksPerDay() > lock.getTaskTimes().stream().map(LocalDateTime::toLocalDate)
                            .filter(yesterday::equals).count()) {
                        violation = true;
                    }
                }
                if (lock.getMinSpinsPerDay() != null) {
                    if (lock.getMinSpinsPerDay() > lock.getSpinningWheelTimes().stream()
                            .map(LocalDateTime::toLocalDate).filter(yesterday::equals).count()) {
                        violation = true;
                    }
                }
                if (violation) {
                    applyPenalty();
                }
                lock.setLastCheck(today);
                timeLockRepository.save(lock);
            }
        }
    }

    public void pillory(CommunityPilloryReason reason, UUID keyholderId) {
        CommunityPilloryEntity pillory = new CommunityPilloryEntity();
        pillory.setCreatedAt(LocalDateTime.now());
        pillory.setLockeeId(lock.getLockee());
        pillory.setLockId(lock.getLockId());
        pillory.setReason(reason);
        pillory.setKeyholderId(keyholderId);
        pilloryRepository.save(pillory);
    }

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

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

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

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

    @Override
    public int getUnlockcodeLenght() {
        return lock.getUnlockCodeLength();
    }
}
