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

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

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

import de.oaa.xxx.games.chastity.cardlock.CardlockRepository;
import de.oaa.xxx.games.chastity.community.CommunityTaskVoteEntity;
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.timelock.TimeLockRepository;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationEntity;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceEntity;
import de.oaa.xxx.games.chastity.keyholder.KeyholderTaskChoiceRepository;
import de.oaa.xxx.games.chastity.keyholder.KeyholderVerificationRepository;
import de.oaa.xxx.games.chastity.tasks.Task;
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
import de.oaa.xxx.games.chastity.unlock.UnlockCodeHistoryService;
import de.oaa.xxx.games.history.GameHistoryEntity;
import de.oaa.xxx.games.history.GameHistoryRepository;
import de.oaa.xxx.games.history.GameRole;
import de.oaa.xxx.games.history.GameType;
import de.oaa.xxx.social.SystemMessageService;
import de.oaa.xxx.user.UserRepository;

public abstract class BaseLockService {

    private static final Logger LOGGER = LoggerFactory.getLogger(BaseLockService.class);

    protected final CommunityVerificationVoteRepository communityVerificationVoteRepository;
    protected final CommunityVerificationRepository communityVerificationRepository;
    protected final KeyholderVerificationRepository keyholderVerificationRepository;
    protected final GameHistoryRepository gameHistoryRepository;
    protected final UserRepository userRepository;
    protected final KeyholderNotificationRepository keyholderNotificationRepository;
    protected final SystemMessageService systemMessageService;
    protected final UnlockCodeHistoryService unlockCodeHistoryService;
    protected final KeyholderTaskChoiceRepository keyholderTaskChoiceRepository;
    protected final CommunityTaskVoteRepository communityTaskVoteRepository;

    /** Wird von Subklassen gesetzt; steuert wie das physische Schloss (neu) verriegelt wird. */
    protected de.oaa.xxx.games.chastity.lockcontroll.LockControl lockControl;

    public de.oaa.xxx.games.chastity.lockcontroll.LockControl getLockControl() {
        return lockControl;
    }

    // ── Abstrakte Methoden ────────────────────────────────────────────────────

    protected abstract BaseLockEntity getLock();
    protected abstract void saveLock();
    protected abstract GameType getGameType();
    /** Wie wird Überschreitung der Hygiene-Öffnung bestraft? CardLock friert ein, TimeLock verlängert die Zeit. */
    protected abstract void applyHygieneOvertime(Long overtime);

    // ── Hook-Methoden (Standard: No-Op) ───────────────────────────────────────

    /** TimeLock: lockControl.unlock() vor dem finalen Entsperren aufrufen. */
    protected void beforePhysicalUnlock() {}
    /** TimeLock: lockControl.lock() nach dem Schließen der Hygiene-Öffnung aufrufen. */
    protected void afterHygieneClosing() {}

    public BaseLockService(
            CommunityVerificationVoteRepository communityVerificationVoteRepository,
            CommunityVerificationRepository communityVerificationRepository,
            KeyholderVerificationRepository keyholderVerificationRepository,
            GameHistoryRepository gameHistoryRepository,
            UserRepository userRepository,
            KeyholderNotificationRepository keyholderNotificationRepository,
            SystemMessageService systemMessageService,
            UnlockCodeHistoryService unlockCodeHistoryService,
            KeyholderTaskChoiceRepository keyholderTaskChoiceRepository,
            CommunityTaskVoteRepository communityTaskVoteRepository) {
        this.communityVerificationVoteRepository = communityVerificationVoteRepository;
        this.communityVerificationRepository = communityVerificationRepository;
        this.keyholderVerificationRepository = keyholderVerificationRepository;
        this.gameHistoryRepository = gameHistoryRepository;
        this.userRepository = userRepository;
        this.keyholderNotificationRepository = keyholderNotificationRepository;
        this.systemMessageService = systemMessageService;
        this.unlockCodeHistoryService = unlockCodeHistoryService;
        this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository;
        this.communityTaskVoteRepository = communityTaskVoteRepository;
    }

    // ── Lockee-Prüfung ────────────────────────────────────────────────────────

    /**
     * Prüft ob der Anwender bereits ein aktives Lock (CardLock oder TimeLock) als Lockee hat.
     * Ein Lock gilt als aktiv wenn startTime gesetzt und unlockTime null ist.
     */
    public static boolean hasActiveLock(UUID lockeeId, CardlockRepository cardlockRepo,
            TimeLockRepository timelockRepo) {
        return cardlockRepo.existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(lockeeId)
                || timelockRepo.existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(lockeeId);
    }

    // ── Gemeinsame Hilfsmethoden ──────────────────────────────────────────────

    protected Long calcOvertime() {
        LocalDateTime now = LocalDateTime.now();
        BaseLockEntity lock = getLock();
        if (lock.getTempOpeningTime() != null && lock.getTempOpeningDuration() != null) {
            LocalDateTime dueTime = lock.getTempOpeningTime().plusMinutes(lock.getTempOpeningDuration());
            if (now.isAfter(dueTime)) {
                return ChronoUnit.MINUTES.between(dueTime, now);
            }
        }
        return null;
    }

    protected void reportKeyholder(Long overtime) {
        BaseLockEntity lock = getLock();
        KeyholderNotificationEntity notification = new KeyholderNotificationEntity();
        notification.setLockId(lock.getLockId());
        notification.setLockeeId(lock.getLockee());
        notification.setKeyholderUserId(lock.getKeyholder());
        notification.setViolationTime(LocalDateTime.now());
        notification.setOvertimeMinutes(overtime);
        notification.setOpeningReason(de.oaa.xxx.games.chastity.unlock.TempOpeningReason.HYGIENE);
        keyholderNotificationRepository.save(notification);
        userRepository.findById(lock.getKeyholder()).ifPresent(kh ->
                sendMessage(lock.getLockee(), kh.getUserId(),
                        "Deine Lockee hat die Hygiene-Öffnung um " + overtime + " Minuten überschritten.",
                        "/games/chastity/keyholder.html?lockId=" + lock.getLockId(),
                        de.oaa.xxx.social.entity.MessageCause.GAME_STATE));
    }

    protected void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl,
            de.oaa.xxx.social.entity.MessageCause cause) {
        systemMessageService.send(senderId, receiverId, text, targetUrl, cause);
    }

    // ── Aufgaben ──────────────────────────────────────────────────────────────

    public void task(Task task) {
        BaseLockEntity lock = getLock();
        LOGGER.debug("Apply task {}", task);
        lock.setCurrentTask(task.getTitle());
        lock.setCurrentTaskDescription(task.getDescription());
        if (task.getMinutes() != null && task.getMinutes() > 0) {
            lock.setTaskUntil(LocalDateTime.now().plusMinutes(task.getMinutes()));
        }
    }

    public String clearTask() {
        BaseLockEntity lock = getLock();
        LOGGER.debug("Clear task");
        lock.setCurrentTask(null);
        lock.setCurrentTaskDescription(null);
        lock.setTaskUntil(null);
        return "";
    }

    protected void applyRandomTask() {
        LOGGER.debug("Apply random task");
        var tasks = getLock().getTasks();
        if (tasks != null && !tasks.isEmpty()) {
            task(tasks.get(new Random().nextInt(tasks.size())));
        }
    }

    protected void startKeyholderVote() {
        BaseLockEntity lock = getLock();
        KeyholderTaskChoiceEntity choice = new KeyholderTaskChoiceEntity();
        choice.setLockId(lock.getLockId());
        choice.setCreatedAt(LocalDateTime.now());
        choice.setActive(true);
        choice.setExpiresAt(LocalDateTime.now().plusHours(1));
        keyholderTaskChoiceRepository.save(choice);
        userRepository.findById(lock.getKeyholder())
                .ifPresent(kh -> sendMessage(lock.getLockee(), kh.getUserId(),
                        "Deine Lockee hat eine Aufgaben-Karte gezogen – wähle eine Aufgabe aus.",
                        "/games/chastity/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE));
    }

    protected void startCommunityVote() {
        BaseLockEntity lock = getLock();
        CommunityTaskVoteEntity vote = new CommunityTaskVoteEntity();
        vote.setLockId(lock.getLockId());
        vote.setCreatedAt(LocalDateTime.now());
        vote.setExpiresAt(LocalDateTime.now().plusHours(1));
        vote.setActive(true);
        communityTaskVoteRepository.save(vote);
    }

    // ── Temporäre Öffnung ─────────────────────────────────────────────────────

    protected void startTempOpening(TempOpeningReason reason, Integer duration) {
        BaseLockEntity lock = getLock();
        assert duration != null;
        lock.setTempOpeningReason(reason);
        lock.setTempOpeningTime(LocalDateTime.now());
        lock.setTempOpeningDuration(duration);
        saveLock();
        unlockCodeHistoryService.save(lock.getLockee(), lock.getLockId(), lock.getName(), lock.getUnlockCode(), reason.toString());
    }

    public String endTempOpening() {
        var lock = getLock();
        var now = LocalDateTime.now();
        var overtime = calcOvertime();
        if (overtime != null) {
            if (lock.getKeyholder() != null) {
                reportKeyholder(overtime);
            }
            applyHygieneOvertime(overtime);
        }
        afterHygieneClosing();
        if (TempOpeningReason.HYGIENE == lock.getTempOpeningReason()) {        	
        	lock.setLastHygineOpening(now);
        }
        lock.setTempOpeningReason(null);
        lock.setTempOpeningDuration(null);
        lock.setTempOpeningTime(null);
        if (lockControl != null
                && lock.getControllType() != de.oaa.xxx.games.chastity.lockcontroll.LockControllType.UNLOCK_CODE) {
        	lockControl.lock();
            saveLock();
            return lock.getUnlockCode() != null ? lock.getUnlockCode() : "";
        }
        var code = CodeCreator.createNumeric(lock.getUnlockCodeLength() != null ? lock.getUnlockCodeLength() : 5);
        lock.setUnlockCode(code);
        saveLock();
        return code;
    }

    // ── Entsperren ────────────────────────────────────────────────────────────

    public void unlock(String unlockCode) {
        BaseLockEntity lock = getLock();
        beforePhysicalUnlock();
        lock.setUnlockTime(LocalDateTime.now());

        boolean valid = true;
        if (lock.isEmergencyAutoUnlocked()) {
            valid = false;
            LOGGER.debug("Lock invalid - Emergency Auto-Unlock (1h timer)");
        } else if (lock.isTestLock()) {
            valid = false;
        } else if (Duration.between(lock.getStartTime(), lock.getUnlockTime()).toHours() > 24) {
            Set<LocalDate> verifications;
            if (lock.getKeyholder() != null) {
                verifications = keyholderVerificationRepository.findByLockId(lock.getLockId()).stream()
                        .filter(v -> v.isValid())
                        .map(v -> v.getVerificationDate())
                        .collect(Collectors.toSet());
            } else {
                verifications = communityVerificationRepository.findByLockId(lock.getLockId()).stream()
                        .filter(v -> v.isValid())
                        .map(v -> v.getVerificationDate())
                        .collect(Collectors.toSet());
            }
            LocalDate current = lock.getStartTime().toLocalDate();
            LocalDate last = lock.getUnlockTime().toLocalDate().minusDays(1);
            while (!current.isAfter(last)) {
                if (!verifications.contains(current)) {
                    valid = false;
                    LOGGER.debug("Lock invalid - no daily verification on {}", current);
                    break;
                }
                current = current.plusDays(1);
            }
        }

        LOGGER.debug("Unlocked at {}", lock.getUnlockTime());
        saveLock();

        if (lockControl != null) {
            lockControl.cleanup();
        }

        if (valid) {
            long durationMinutes = Duration.between(lock.getStartTime(), lock.getUnlockTime()).toMinutes();
            GameHistoryEntity entry = new GameHistoryEntity();
            entry.setGameType(getGameType());
            entry.setGameName(lock.getName());
            entry.setStartTime(lock.getStartTime());
            entry.setEndTime(lock.getUnlockTime());
            entry.setDurationMinutes(durationMinutes);
            entry.addParticipant(lock.getLockee(), GameRole.LOCKEE);
            if (lock.getKeyholder() != null) {
                entry.addParticipant(lock.getKeyholder(), GameRole.KEYHOLDER);
            }
            gameHistoryRepository.save(entry);

            int minutes = (int) durationMinutes;
            userRepository.findById(lock.getLockee()).ifPresent(u -> {
                u.setLockeeXp(u.getLockeeXp() + minutes);
                userRepository.save(u);
            });
            if (lock.getKeyholder() != null) {
                userRepository.findById(lock.getKeyholder()).ifPresent(u -> {
                    u.setKeyholderXp(u.getKeyholderXp() + minutes);
                    userRepository.save(u);
                });
            }
        }
    }
}
