package com.alttd.altitudeweb.controllers.history;

import com.alttd.altitudeweb.api.HistoryApi;
import com.alttd.altitudeweb.controllers.data_from_auth.AuthenticatedUuid;
import com.alttd.altitudeweb.services.limits.RateLimit;
import com.alttd.altitudeweb.model.HistoryCountDto;
import com.alttd.altitudeweb.model.PunishmentHistoryListDto;
import com.alttd.altitudeweb.setup.Connection;
import com.alttd.altitudeweb.database.Databases;
import com.alttd.altitudeweb.database.litebans.*;
import com.alttd.altitudeweb.model.PunishmentHistoryDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Slf4j
@RestController
@RateLimit(limit = 30, timeValue = 10, timeUnit = TimeUnit.SECONDS)
@RequiredArgsConstructor
public class HistoryApiController implements HistoryApi {

    private final AuthenticatedUuid authenticatedUuid;

    @Override
    public ResponseEntity<PunishmentHistoryListDto> getHistoryForAll(String userType, String type, Integer page) {
        return getHistoryForUsers(userType, type, "", page);
    }

    @Override
    public ResponseEntity<PunishmentHistoryListDto> getHistoryForUsers(String userType, String type, String user, Integer page) {
        UserType userTypeEnum = UserType.getUserType(userType);
        HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
        PunishmentHistoryListDto punishmentHistoryList = new PunishmentHistoryListDto();
        CompletableFuture<List<HistoryRecord>> historyRecords = new CompletableFuture<>();

        Connection.getConnection(Databases.LITE_BANS)
                .runQuery(sqlSession -> {
                    log.debug("Loading history through name for type {}", type);
                    try {
                        List<HistoryRecord> temp = sqlSession.getMapper(NameHistoryMapper.class)
                                .getRecent(historyTypeEnum, userTypeEnum, user, page);
                        historyRecords.complete(temp);
                    } catch (Exception e) {
                        log.error("Failed to load history through name for type {}", type, e);
                        historyRecords.completeExceptionally(e);
                    }
                });
        return mapPunishmentHistory(punishmentHistoryList, historyRecords);
    }

    @Override
    public ResponseEntity<PunishmentHistoryListDto> getHistoryForUuid(String userType, String type, String uuid, Integer page) {
        UserType userTypeEnum = UserType.getUserType(userType);
        HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
        PunishmentHistoryListDto punishmentHistoryList = new PunishmentHistoryListDto();
        CompletableFuture<List<HistoryRecord>> historyRecords = new CompletableFuture<>();

        Connection.getConnection(Databases.LITE_BANS)
                .runQuery(sqlSession -> {
                    log.debug("Loading history through uuid for type {}", type);
                    try {
                        List<HistoryRecord> temp = sqlSession.getMapper(UUIDHistoryMapper.class)
                                .getRecent(historyTypeEnum, userTypeEnum, UUID.fromString(uuid), page);
                        historyRecords.complete(temp);
                    } catch (Exception e) {
                        log.error("Failed to load history through uuid for type {}", type, e);
                        historyRecords.completeExceptionally(e);
                    }
                });
        return mapPunishmentHistory(punishmentHistoryList, historyRecords);
    }

    @Override
    public ResponseEntity<List<String>> getUserNames(String userType, String type) {
        UserType userTypeEnum = UserType.getUserType(userType);
        HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
        CompletableFuture<List<String>> playerGroupFuture = new CompletableFuture<>();

        Connection.getConnection(Databases.LITE_BANS)
                .runQuery(sqlSession -> {
                    log.debug("Loading user names for type {}", type);
                    try {
                        List<String> temp = sqlSession.getMapper(RecentNamesMapper.class)
                                .getRecent(historyTypeEnum, userTypeEnum);
                        playerGroupFuture.complete(temp);
                    } catch (Exception e) {
                        log.error("Failed to load user names for type {}", type, e);
                        playerGroupFuture.completeExceptionally(e);
                    }
                });
        return ResponseEntity.ok().body(playerGroupFuture.join());
    }

    @Override
    public ResponseEntity<HistoryCountDto> getTotalPunishments() {
        CompletableFuture<HistoryCount> historyCountCompletableFuture = new CompletableFuture<>();

        Connection.getConnection(Databases.LITE_BANS)
                .runQuery(sqlSession -> {
                    log.debug("Loading history count");
                    try {
                        HistoryCount punishmentCounts = sqlSession.getMapper(HistoryCountMapper.class)
                                .getPunishmentCounts();
                        historyCountCompletableFuture.complete(punishmentCounts);
                    } catch (Exception e) {
                        log.error("Failed to load history count", e);
                        historyCountCompletableFuture.completeExceptionally(e);
                    }
                });
        return mapHistoryCount(historyCountCompletableFuture.join());
    }

    @Override
    public ResponseEntity<Integer> getTotalResultsForUserSearch(String userType, String type, String user) {
        UserType userTypeEnum = UserType.getUserType(userType);
        HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
        CompletableFuture<Integer> searchResultCountCompletableFuture = new CompletableFuture<>();

        Connection.getConnection(Databases.LITE_BANS)
                .runQuery(sqlSession -> {
                    log.debug("Loading name search result count");
                    try {
                        Integer punishmentCount = sqlSession.getMapper(HistoryCountMapper.class)
                                .getNamePunishmentCount(historyTypeEnum, userTypeEnum, user);
                        searchResultCountCompletableFuture.complete(punishmentCount);
                    } catch (Exception e) {
                        log.error("Failed to load history count", e);
                        searchResultCountCompletableFuture.completeExceptionally(e);
                    }
                });
        return ResponseEntity.ok().body(searchResultCountCompletableFuture.join());
    }

    @Override
    public ResponseEntity<Integer> getTotalResultsForUuidSearch(String userType, String type, String uuid) {
        UserType userTypeEnum = UserType.getUserType(userType);
        HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
        CompletableFuture<Integer> searchResultCountCompletableFuture = new CompletableFuture<>();

        Connection.getConnection(Databases.LITE_BANS)
                .runQuery(sqlSession -> {
                    log.debug("Loading uuid search result count");
                    try {
                        Integer punishmentCount = sqlSession.getMapper(HistoryCountMapper.class)
                                .getUuidPunishmentCount(historyTypeEnum, userTypeEnum, UUID.fromString(uuid));
                        searchResultCountCompletableFuture.complete(punishmentCount);
                    } catch (Exception e) {
                        log.error("Failed to load history count", e);
                        searchResultCountCompletableFuture.completeExceptionally(e);
                    }
                });
        return ResponseEntity.ok().body(searchResultCountCompletableFuture.join());
    }

    @Override
    public ResponseEntity<PunishmentHistoryListDto> getAllHistoryForUUID(String uuid) {
        PunishmentHistoryListDto punishmentHistoryList = new PunishmentHistoryListDto();
        CompletableFuture<List<HistoryRecord>> historyRecordsCompletableFuture = new CompletableFuture<>();
        Connection.getConnection(Databases.LITE_BANS).runQuery(sqlSession -> {
            log.debug("Loading all history for uuid {}", uuid);
            try {
                List<HistoryRecord> punishments = sqlSession.getMapper(UUIDHistoryMapper.class)
                        .getAllHistoryForUUID(UUID.fromString(uuid));
                historyRecordsCompletableFuture.complete(punishments);
            } catch (Exception e) {
                log.error("Failed to load all history for uuid {}", uuid, e);
                historyRecordsCompletableFuture.completeExceptionally(e);
            }
        });
        return mapPunishmentHistory(punishmentHistoryList, historyRecordsCompletableFuture);
    }

    @Override
    public ResponseEntity<PunishmentHistoryDto> getHistoryById(String type, Integer id) {
        HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
        CompletableFuture<HistoryRecord> historyRecordCompletableFuture = new CompletableFuture<>();

        Connection.getConnection(Databases.LITE_BANS)
                .runQuery(sqlSession -> {
                    log.debug("Loading history by id");
                    try {
                        HistoryRecord punishment = sqlSession.getMapper(IdHistoryMapper.class)
                                .getRecentHistory(historyTypeEnum, id);
                        historyRecordCompletableFuture.complete(punishment);
                    } catch (Exception e) {
                        log.error("Failed to load history count", e);
                        historyRecordCompletableFuture.completeExceptionally(e);
                    }
                });
        return ResponseEntity.ok().body(mapPunishmentHistory(historyRecordCompletableFuture.join()));
    }

    private ResponseEntity<HistoryCountDto> mapHistoryCount(HistoryCount historyCount) {
        HistoryCountDto historyCountDto = new HistoryCountDto();
        historyCountDto.setBans(historyCount.getBans());
        historyCountDto.setMutes(historyCount.getMutes());
        historyCountDto.setWarnings(historyCount.getWarnings());
        historyCountDto.setKicks(historyCount.getKicks());
        return ResponseEntity.ok().body(historyCountDto);
    }

    private ResponseEntity<PunishmentHistoryListDto> mapPunishmentHistory(PunishmentHistoryListDto punishmentHistoryList, CompletableFuture<List<HistoryRecord>> historyRecords) {
        historyRecords.join().forEach(historyRecord -> {
            PunishmentHistoryDto innerDto = mapPunishmentHistory(historyRecord);
            punishmentHistoryList.add(innerDto);
        });
        return ResponseEntity.ok().body(punishmentHistoryList);
    }

    private static PunishmentHistoryDto mapPunishmentHistory(HistoryRecord historyRecord) {
        PunishmentHistoryDto.TypeEnum type = switch (historyRecord.getType().toLowerCase()) {
            case "ban" -> PunishmentHistoryDto.TypeEnum.BAN;
            case "mute" -> PunishmentHistoryDto.TypeEnum.MUTE;
            case "warn" -> PunishmentHistoryDto.TypeEnum.WARN;
            case "kick" -> PunishmentHistoryDto.TypeEnum.KICK;
            default -> throw new IllegalStateException("Unexpected value: " + historyRecord.getType());
        };
        return new PunishmentHistoryDto()
                .uuid(historyRecord.getUuid())
                .username(historyRecord.getPunishedName())
                .reason(historyRecord.getReason())
                .punishedByUuid(historyRecord.getBannedByUuid())
                .punishedBy(historyRecord.getBannedByName())
                .removedBy(historyRecord.getRemovedByName())
                .punishmentTime(historyRecord.getTime())
                .expiryTime(historyRecord.getUntil())
                .removedReason(historyRecord.getRemovedByReason())
                .type(type)
                .id(historyRecord.getId());
    }

    @Override
    public ResponseEntity<PunishmentHistoryDto> updatePunishmentReason(String type, Integer id, String reason) {
        log.debug("Updating reason for {} id {} to {}", type, id, reason);
        HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
        CompletableFuture<PunishmentHistoryDto> result = new CompletableFuture<>();

        final UUID actor = authenticatedUuid.getAuthenticatedUserUuid();

        Connection.getConnection(Databases.LITE_BANS).runQuery(sqlSession -> {
            try {
                IdHistoryMapper idMapper = sqlSession.getMapper(IdHistoryMapper.class);
                EditHistoryMapper editMapper = sqlSession.getMapper(EditHistoryMapper.class);
                HistoryRecord before = idMapper.getRecentHistory(historyTypeEnum, id);
                if (before == null) {
                    result.complete(null);
                    return;
                }
                int changed = editMapper.setReason(historyTypeEnum, id, reason);
                HistoryRecord after = idMapper.getRecentHistory(historyTypeEnum, id);
                log.info("[Punishment Edit] Actor={} Type={} Id={} Reason: '{}' -> '{}' (rows={})",
                         actor, historyTypeEnum, id, before.getReason(), after != null ? after.getReason() : null, changed);
                result.complete(after != null ? mapPunishmentHistory(after) : null);
            } catch (Exception e) {
                log.error("Failed to update reason for {} id {}", type, id, e);
                result.completeExceptionally(e);
            }
        });
        PunishmentHistoryDto body = result.join();
        if (body == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(body);
    }

    @Override
    public ResponseEntity<PunishmentHistoryDto> updatePunishmentUntil(String type, Integer id, Long until) {
        log.debug("Updating until for {} id {} to {}", type, id, until);
        HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
        CompletableFuture<PunishmentHistoryDto> result = new CompletableFuture<>();

        final UUID actor = authenticatedUuid.getAuthenticatedUserUuid();

        Connection.getConnection(Databases.LITE_BANS).runQuery(sqlSession -> {
            try {
                IdHistoryMapper idMapper = sqlSession.getMapper(IdHistoryMapper.class);
                EditHistoryMapper editMapper = sqlSession.getMapper(EditHistoryMapper.class);
                HistoryRecord before = idMapper.getRecentHistory(historyTypeEnum, id);
                if (before == null) {
                    result.complete(null);
                    return;
                }
                int changed = editMapper.setUntil(historyTypeEnum, id, until);
                HistoryRecord after = idMapper.getRecentHistory(historyTypeEnum, id);
                log.info("[Punishment Edit] Actor={} Type={} Id={} Until: '{}' -> '{}' (rows={})",
                         actor, historyTypeEnum, id, before.getUntil(), after != null ? after.getUntil() : null, changed);
                result.complete(after != null ? mapPunishmentHistory(after) : null);
            } catch (IllegalArgumentException e) {
                log.warn("Invalid until edit for type {} id {}: {}", type, id, e.getMessage());
                result.complete(null);
            } catch (Exception e) {
                log.error("Failed to update until for {} id {}", type, id, e);
                result.completeExceptionally(e);
            }
        });
        PunishmentHistoryDto body = result.join();
        if (body == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(body);
    }

    @Override
    public ResponseEntity<Void> removePunishment(String type, Integer id) {
        log.debug("Removing punishment for {} id {}", type, id);
        HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
        CompletableFuture<Boolean> result = new CompletableFuture<>();

        final UUID actorUuid = authenticatedUuid.getAuthenticatedUserUuid();

        Connection.getConnection(Databases.LITE_BANS).runQuery(sqlSession -> {
            try {
                IdHistoryMapper idMapper = sqlSession.getMapper(IdHistoryMapper.class);
                EditHistoryMapper editMapper = sqlSession.getMapper(EditHistoryMapper.class);
                HistoryRecord before = idMapper.getRecentHistory(historyTypeEnum, id);
                if (before == null) {
                    result.complete(false);
                    return;
                }
                String actorName = sqlSession.getMapper(RecentNamesMapper.class).getUsername(actorUuid.toString());
                int changed = editMapper.remove(historyTypeEnum, id);
                log.info("[Punishment Remove] Actor={} ({}) Type={} Id={} Before(active={} removedBy={} reason='{}') (rows={})",
                         actorName, actorUuid, historyTypeEnum, id,
                         before.getRemovedByName() == null ? 1 : 0, before.getRemovedByName(), before.getRemovedByReason(),
                         changed);
                result.complete(changed > 0);
            } catch (Exception e) {
                log.error("Failed to remove punishment for {} id {}", type, id, e);
                result.completeExceptionally(e);
            }
        });
        Boolean ok = result.join();
        if (ok == null || !ok) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.noContent().build();
    }
}
