package com.alttd.altitudeweb.controllers.forms;

import com.alttd.altitudeweb.api.AppealsApi;
import com.alttd.altitudeweb.database.Databases;
import com.alttd.altitudeweb.database.litebans.HistoryRecord;
import com.alttd.altitudeweb.database.litebans.HistoryType;
import com.alttd.altitudeweb.database.litebans.IdHistoryMapper;
import com.alttd.altitudeweb.database.web_db.forms.Appeal;
import com.alttd.altitudeweb.database.web_db.forms.AppealMapper;
import com.alttd.altitudeweb.database.web_db.mail.EmailVerification;
import com.alttd.altitudeweb.database.web_db.mail.EmailVerificationMapper;
import com.alttd.altitudeweb.mappers.AppealDataMapper;
import com.alttd.altitudeweb.model.*;
import com.alttd.altitudeweb.services.forms.DiscordAppeal;
import com.alttd.altitudeweb.services.limits.RateLimit;
import com.alttd.altitudeweb.services.mail.AppealMail;
import com.alttd.altitudeweb.setup.Connection;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Slf4j
@RestController
@AllArgsConstructor
@RateLimit(limit = 30, timeValue = 1, timeUnit = TimeUnit.HOURS)
public class AppealController implements AppealsApi {

    private final AppealDataMapper mapper;
    private final AppealMail appealMail;
    private final DiscordAppeal discordAppeal;
    private final com.alttd.altitudeweb.services.discord.AppealDiscord appealDiscord;

    @Override
    public ResponseEntity<BannedUserResponseDto> getBannedUser(String discordId) throws Exception {
        long discordIdAsLong = Long.parseLong(discordId);
        return new ResponseEntity<>(discordAppeal.getBannedUser(discordIdAsLong), HttpStatus.OK);
    }

    @RateLimit(limit = 3, timeValue = 1, timeUnit = TimeUnit.HOURS, key = "discordAppeal")
    @Override
    public ResponseEntity<FormResponseDto> submitDiscordAppeal(DiscordAppealDto discordAppealDto) {
        return new ResponseEntity<>(discordAppeal.submitAppeal(discordAppealDto), HttpStatus.OK);
    }

    @RateLimit(limit = 3, timeValue = 1, timeUnit = TimeUnit.HOURS, key = "minecraftAppeal")
    @Override
    public ResponseEntity<FormResponseDto> submitMinecraftAppeal(MinecraftAppealDto minecraftAppealDto) {
        boolean success = true;
        CompletableFuture<Appeal> appealCompletableFuture = new CompletableFuture<>();

        Connection.getConnection(Databases.DEFAULT)
                  .runQuery(sqlSession -> {
                      log.debug("Loading history by id");
                      try {
                          Appeal appeal = mapper.minecraftAppealDtoToAppeal(minecraftAppealDto);
                          sqlSession.getMapper(AppealMapper.class).createAppeal(appeal);
                          appealCompletableFuture.complete(appeal);
                      } catch (Exception e) {
                          log.error("Failed to load history count", e);
                          appealCompletableFuture.completeExceptionally(e);
                      }
                  });
        Appeal appeal = appealCompletableFuture.join();
        HistoryRecord history = getHistory(appeal.historyType(), appeal.historyId());
        if (history == null) {
            throw new ResponseStatusException(HttpStatusCode.valueOf(404), "History not found");
        }
        // Send to Discord channels
        try {
            appealDiscord.sendAppealToDiscord(appeal, history);
        } catch (Exception e) {
            log.error("Failed to send appeal {} to Discord", appeal.id(), e);
            success = false;
        }

        appealMail.sendAppealNotification(appeal, history);

        CompletableFuture<Optional<EmailVerification>> emailVerificationCompletableFuture = new CompletableFuture<>();
        Connection.getConnection(Databases.DEFAULT)
                .runQuery(sqlSession -> {
                    log.debug("Retrieving mail by uuid and address");

                    EmailVerification verifiedMail = sqlSession.getMapper(EmailVerificationMapper.class)
                                                                 .findByUserAndEmail(appeal.uuid(), appeal.email().toLowerCase());
                    emailVerificationCompletableFuture.complete(Optional.ofNullable(verifiedMail));
                });
        Optional<EmailVerification> optionalEmailVerification = emailVerificationCompletableFuture.join();

        if (optionalEmailVerification.isEmpty()) {
            return ResponseEntity.badRequest().build();
        }
        EmailVerification emailVerification = optionalEmailVerification.get();
        if (!emailVerification.verified()) {
            return ResponseEntity.badRequest().build();
        }
        if (!success) {
            return ResponseEntity.internalServerError().build();
        }
        Connection.getConnection(Databases.DEFAULT)
                .runQuery(sqlSession -> {
                    log.debug("Marking appeal {} as sent", appeal.id());
                    sqlSession.getMapper(AppealMapper.class)
                            .markAppealAsSent(appeal.id());
                });
        FormResponseDto appealResponseDto = new FormResponseDto(
                appeal.id().toString(),
                "Your appeal has been submitted. You will be notified when it has been reviewed.",
                true);
        return ResponseEntity.ok().body(appealResponseDto);
    }

    @Override
    public ResponseEntity<FormResponseDto> updateMail(UpdateMailDto updateMailDto) {
        //TODO move to its own endpoint
        throw new ResponseStatusException(HttpStatusCode.valueOf(501), "Updating mail is not yet supported");
    }

    private HistoryRecord getHistory(String type, int 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 historyRecordCompletableFuture.join();
    }
}
