package com.alttd.altitudeweb.services.forms;

import com.alttd.altitudeweb.controllers.data_from_auth.AuthenticatedUuid;
import com.alttd.altitudeweb.database.Databases;
import com.alttd.altitudeweb.database.luckperms.UUIDUsernameMapper;
import com.alttd.altitudeweb.database.web_db.forms.DiscordAppealMapper;
import com.alttd.altitudeweb.database.web_db.mail.EmailVerification;
import com.alttd.altitudeweb.database.web_db.mail.EmailVerificationMapper;
import com.alttd.altitudeweb.mappers.BannedUserToBannedUserDtoMapper;
import com.alttd.altitudeweb.mappers.DiscordAppealDtoToDiscordAppealMapper;
import com.alttd.altitudeweb.model.BannedUserResponseDto;
import com.alttd.altitudeweb.model.DiscordAppealDto;
import com.alttd.altitudeweb.model.FormResponseDto;
import com.alttd.altitudeweb.services.discord.AppealDiscord;
import com.alttd.altitudeweb.services.mail.AppealMail;
import com.alttd.altitudeweb.setup.Connection;
import com.alttd.webinterface.appeals.BannedUser;
import com.alttd.webinterface.appeals.DiscordAppealDiscord;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

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

@Slf4j
@Service
@RequiredArgsConstructor
public class DiscordAppeal {

    private final BannedUserToBannedUserDtoMapper bannedUserToBannedUserDtoMapper;
    private final DiscordAppealDtoToDiscordAppealMapper discordAppealDtoToDiscordAppealMapper;
    private final AuthenticatedUuid authenticatedUuid;
    private final AppealDiscord appealDiscord;
    private final AppealMail appealMail;

    public BannedUserResponseDto getBannedUser(Long discordId) {
        DiscordAppealDiscord discordAppeal = DiscordAppealDiscord.getInstance();
        Optional<BannedUser> join = discordAppeal.getBannedUser(discordId).join();
        if (join.isEmpty()) {
            return new BannedUserResponseDto(false);
        }
        BannedUserResponseDto bannedUserResponseDto = new BannedUserResponseDto(true);
        bannedUserResponseDto.setBannedUser(bannedUserToBannedUserDtoMapper.map(join.get()));
        return bannedUserResponseDto;
    }

    public FormResponseDto submitAppeal(DiscordAppealDto discordAppealDto) {
        DiscordAppealDiscord discordAppealDiscord = DiscordAppealDiscord.getInstance();
        long discordId = Long.parseLong(discordAppealDto.getDiscordId());
        Optional<BannedUser> join = discordAppealDiscord.getBannedUser(discordId).join();
        if (join.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found");
        }
        BannedUser bannedUser = join.get();

        Optional<UUID> optionalUUID = authenticatedUuid.tryGetAuthenticatedUserUuid();
        if (optionalUUID.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not authenticated");
        }
        UUID uuid = optionalUUID.get();
        CompletableFuture<com.alttd.altitudeweb.database.web_db.forms.DiscordAppeal> appealCompletableFuture = new CompletableFuture<>();

        Connection.getConnection(Databases.DEFAULT)
                .runQuery(sqlSession -> {
                    log.debug("Loading history by id");
                    try {
                        com.alttd.altitudeweb.database.web_db.forms.DiscordAppeal discordAppealRecord = discordAppealDtoToDiscordAppealMapper
                                .map(discordAppealDto, uuid, bannedUser.name());
                        sqlSession.getMapper(DiscordAppealMapper.class).createDiscordAppeal(discordAppealRecord);
                        appealCompletableFuture.complete(discordAppealRecord);
                    } catch (Exception e) {
                        log.error("Failed to load history count", e);
                        appealCompletableFuture.completeExceptionally(e);
                    }
                });
        com.alttd.altitudeweb.database.web_db.forms.DiscordAppeal discordAppeal = appealCompletableFuture.join();

        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(discordAppeal.uuid(), discordAppeal.email().toLowerCase());
                    emailVerificationCompletableFuture.complete(Optional.ofNullable(verifiedMail));
                });
        Optional<EmailVerification> optionalEmailVerification = emailVerificationCompletableFuture.join();

        if (optionalEmailVerification.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid mail");
        }
        EmailVerification emailVerification = optionalEmailVerification.get();
        if (!emailVerification.verified()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Mail not verified");
        }

        try {
            appealDiscord.sendAppealToDiscord(discordAppeal);
        } catch (Exception e) {
            log.error("Failed to send appeal {} to Discord", discordAppeal.id(), e);
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to send appeal to Discord");
        }
        //TODO verify mail
        String username = getUsername(uuid);
        appealMail.sendAppealNotification(discordAppeal, username);

        Connection.getConnection(Databases.DEFAULT)
                .runQuery(sqlSession -> {
                    log.debug("Marking appeal {} as sent", discordAppeal.id());
                    sqlSession.getMapper(DiscordAppealMapper.class)
                            .markDiscordAppealAsSent(discordAppeal.id());
                });
        return new FormResponseDto(
                discordAppeal.id().toString(),
                "Your appeal has been submitted. You will be notified when it has been reviewed.",
                true);
    }

    private String getUsername(UUID uuid) {
        CompletableFuture<String> usernameFuture = new CompletableFuture<>();
        Connection.getConnection(Databases.LUCK_PERMS)
                .runQuery(sqlSession -> {
                    log.debug("Loading username for uuid {}", uuid);
                    try {
                        String username = sqlSession.getMapper(UUIDUsernameMapper.class).getUsernameFromUUID(uuid.toString());
                        usernameFuture.complete(username);
                    } catch (Exception e) {
                        log.error("Failed to load username for uuid {}", uuid, e);
                        usernameFuture.completeExceptionally(e);
                    }
                });
        return usernameFuture.join();
    }
}
