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

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import de.oaa.xxx.games.chastity.common.BaseLockEntity;
import de.oaa.xxx.games.chastity.common.BaseLockRepository;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationEntity;
import de.oaa.xxx.games.chastity.keyholder.KeyholderNotificationRepository;
import de.oaa.xxx.games.chastity.unlock.TempOpeningReason;
import de.oaa.xxx.social.SystemMessageService;
import de.oaa.xxx.social.entity.MessageCause;
import de.oaa.xxx.user.UserRepository;
import lombok.Data;

@RestController
@RequestMapping("/api/ttlock/callback")
public class TTLockCallback {

    private static final Logger LOGGER = LoggerFactory.getLogger(TTLockCallback.class);
    
    private static final int START_WINDOW_MINUTES = 5;

    private final TTLockUserConfigRepository ttLockUserConfigRepository;
    private final BaseLockRepository baseLockRepository;
    private final KeyholderNotificationRepository keyholderNotificationRepository;
    private final UserRepository userRepository;
    private final SystemMessageService systemMessageService;

    public TTLockCallback(TTLockUserConfigRepository ttLockUserConfigRepository,
                          BaseLockRepository baseLockRepository,
                          KeyholderNotificationRepository keyholderNotificationRepository,
                          UserRepository userRepository,
                          SystemMessageService systemMessageService) {
        this.ttLockUserConfigRepository = ttLockUserConfigRepository;
        this.baseLockRepository = baseLockRepository;
        this.keyholderNotificationRepository = keyholderNotificationRepository;
        this.userRepository = userRepository;
        this.systemMessageService = systemMessageService;
    }
	
	@GetMapping
	public String test() {
		return "OK";
	}
	
	@PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public String handleCallback(@RequestParam Map<String, String> allRequestParams) {
		LOGGER.debug("Callback von TTLock erhalten, verarbeite...");
		try {
			var wrapper = parse(allRequestParams);
			if (Integer.valueOf(1).equals(wrapper.getNotifyType())) {
				LOGGER.info("Lock {} wurde aufgeschlossen", wrapper.getLockId());
				checkUser(wrapper);
			} else {
				if (LOGGER.isDebugEnabled()) {					
					LOGGER.debug("Uninteressantes ereignis: {}", wrapper);
				}
			}
			return "1";
		} catch (Exception e) {
			LOGGER.error("Fehler beim Verarbeiten des Callbacks", e);
			return "0";
		}
    }

    @Transactional
    void checkUser(TTLockCallbackWrapper wrapper) {
        var userOpt = ttLockUserConfigRepository.findByLockId(wrapper.getLockId());
        if (userOpt.isEmpty()) {
            LOGGER.warn("TTLock-Öffnung für unbekanntes Lock {} – nicht in XXX-Sphere registriert", wrapper.getLockId());
            return;
        }

        var lockOpt = baseLockRepository.findByLockee(userOpt.get().getUserId());
        if (lockOpt.isEmpty()) {
            LOGGER.debug("Kein aktives Lock für Benutzer {} gefunden", userOpt.get().getUserId());
            return;
        }

        var lock = lockOpt.get();

        if (lock.getKeyholder() == null) {
            LOGGER.debug("Lock {} hat keinen Keyholder – keine Berechtigungsprüfung notwendig", lock.getLockId());
            return;
        }

        // Nur erfolgreiche Öffnungen prüfen
        LockRecord record = wrapper.getRecords() != null && !wrapper.getRecords().isEmpty()
                ? wrapper.getRecords().get(0) : null;
        if (record != null && !Integer.valueOf(1).equals(record.getSuccess())) {
            LOGGER.debug("Öffnungsversuch an Lock {} war nicht erfolgreich – ignoriere", lock.getLockId());
            return;
        }

        if (isOpeningAuthorized(lock)) {
            LOGGER.debug("Öffnung von Lock {} ist berechtigt", lock.getLockId());
        } else {
            LOGGER.warn("Unerlaubte Öffnung von Lock {} erkannt – benachrichtige Keyholder {}",
                    lock.getLockId(), lock.getKeyholder());
            notifyKeyholder(lock);
        }
    }

    /**
     * Prüft, ob eine Öffnung des Schlosses zu diesem Zeitpunkt berechtigt ist.
     * Berechtigt sind:
     * <ul>
     *   <li>Das Spiel ist bereits beendet (unlockTime gesetzt)</li>
     *   <li>Der Keyholder hat die Entsperrung genehmigt (keyholderRequestedUnlock)</li>
     *   <li>Es läuft gerade eine temporäre Öffnung (Hygiene, Karte, Aufgabe)</li>
     *   <li>Das Lock wurde vor Kurzem gestartet – der Anwender hat den Startcode erhalten
     *       und die Übergabe an das physische Schloss ist noch nicht abgeschlossen</li>
     * </ul>
     */
    private boolean isOpeningAuthorized(BaseLockEntity lock) {
        // Spiel beendet
        if (lock.getUnlockTime() != null) return true;

        // Keyholder hat Entsperrung genehmigt
        if (lock.isKeyholderRequestedUnlock()) return true;

        // Aktive temporäre Öffnung (Hygiene, Karte, Aufgabe)
        if (lock.getTempOpeningTime() != null) return true;

        // Start-Fenster: Anwender hat beim Lock-Start den Code erhalten und
        // hat das Schloss physisch noch nicht übergeben (Relock läuft gerade)
        if (lock.getStartTime() != null
                && ChronoUnit.MINUTES.between(lock.getStartTime(), LocalDateTime.now()) <= START_WINDOW_MINUTES) {
            return true;
        }

        return false;
    }

    private void notifyKeyholder(BaseLockEntity lock) {
        KeyholderNotificationEntity notification = new KeyholderNotificationEntity();
        notification.setLockId(lock.getLockId());
        notification.setLockeeId(lock.getLockee());
        notification.setKeyholderUserId(lock.getKeyholder());
        notification.setViolationTime(LocalDateTime.now());
        notification.setOvertimeMinutes(0);
        notification.setNotifiedKeyholder(false);
        notification.setOpeningReason(TempOpeningReason.TTLOCK_UNAUTHORIZED);
        keyholderNotificationRepository.save(notification);
        userRepository.findById(lock.getKeyholder()).ifPresent(kh ->
                systemMessageService.send(lock.getLockee(), kh.getUserId(),
                        "Deine Lockee hat ihr Schloss unerlaubt geöffnet!",
                        "/games/chastity/keyholder.html?lockId=" + lock.getLockId(),
                        MessageCause.GAME_STATE));
    }

	private TTLockCallbackWrapper parse(Map<String, String> params) {
		ObjectMapper mapper = new ObjectMapper();
		TTLockCallbackWrapper wrapper = new TTLockCallbackWrapper();

		try {
			if (params.containsKey("lockId"))
				wrapper.setLockId(Integer.parseInt(params.get("lockId")));
			if (params.containsKey("notifyType"))
				wrapper.setNotifyType(Integer.parseInt(params.get("notifyType")));
			wrapper.setLockMac(params.get("lockMac"));

			String recordsJson = params.get("records");
			if (recordsJson != null && !recordsJson.isEmpty()) {
				List<LockRecord> recordList = mapper.readValue(recordsJson, new TypeReference<List<LockRecord>>() {
				});
				wrapper.setRecords(recordList);
			}
		} catch (Exception e) {
			System.err.println("Fehler beim Parsen des TTLock Callbacks: " + e.getMessage());
		}

		return wrapper;
	}
	
	@Data
	@JsonIgnoreProperties(ignoreUnknown = true)
	public class TTLockCallbackWrapper {
	    private Integer lockId;
	    private Integer notifyType;
	    private String lockMac;
	    private List<LockRecord> records;
	}

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class LockRecord {
        private Integer lockId;
        private Integer electricQuantity;
        private Long serverDate;
        private Integer recordType;
        private Integer success;
        private String lockMac;
        private String keyboardPwd;
        private Long lockDate;
        private String username;
        
        public LocalDateTime getLockDateTime() {
            if (this.lockDate == null || this.lockDate == 0) return null;
            return LocalDateTime.ofInstant(
                Instant.ofEpochMilli(this.lockDate), 
                ZoneId.systemDefault()
            );
        }
    }
}
