package com.alttd.altitudeweb.controllers.forms;

import com.alttd.altitudeweb.api.ApplicationsApi;
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.StaffApplication;
import com.alttd.altitudeweb.database.web_db.forms.StaffApplicationMapper;
import com.alttd.altitudeweb.database.web_db.mail.EmailVerification;
import com.alttd.altitudeweb.database.web_db.mail.EmailVerificationMapper;
import com.alttd.altitudeweb.mappers.StaffApplicationDataMapper;
import com.alttd.altitudeweb.model.FormResponseDto;
import com.alttd.altitudeweb.model.StaffApplicationDto;
import com.alttd.altitudeweb.services.discord.StaffApplicationDiscord;
import com.alttd.altitudeweb.services.limits.RateLimit;
import com.alttd.altitudeweb.services.mail.StaffApplicationMail;
import com.alttd.altitudeweb.setup.Connection;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

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

    private final AuthenticatedUuid authenticatedUuid;
    private final StaffApplicationDataMapper staffApplicationDataMapper;
    private final StaffApplicationMail staffApplicationMail;
    private final StaffApplicationDiscord staffApplicationDiscord;

    private final Instant open = Instant.parse("2025-10-18T00:00:00Z");
    private final Instant close = Instant.parse("2025-10-26T00:00:00Z");

    @Override
    public ResponseEntity<Boolean> getStaffApplicationsIsOpen() {
        return ResponseEntity.ok(isOpen());
    }

    private boolean isOpen() {
        Instant now = Instant.now();
        return !now.isBefore(open) && !now.isAfter(close);
    }

    @Override
    public ResponseEntity<FormResponseDto> submitStaffApplication(StaffApplicationDto staffApplicationDto) {
        if (!isOpen()) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        UUID userUuid = authenticatedUuid.getAuthenticatedUserUuid();

        String email = staffApplicationDto.getEmail() == null ? null : staffApplicationDto.getEmail().toLowerCase();
        Optional<EmailVerification> optionalEmail = fetchEmailVerification(userUuid, email);
        if (optionalEmail.isEmpty() || !optionalEmail.get().verified()) {
            log.warn("User {} attempted to submit an application without a verified email {}", userUuid, email);
            return ResponseEntity.badRequest().build();
        }

        // Map and persist application
        StaffApplication application = staffApplicationDataMapper.map(userUuid, staffApplicationDto);
        saveApplication(application);

        String username = getUsername(userUuid);

        try {
            if (!staffApplicationMail.sendApplicationEmail(username, application)) {
                log.warn("Failed to send staff application email for {}", application.id());
                return ResponseEntity.internalServerError().build();
            }
        } catch (Exception e) {
            log.error("Error while sending staff application email for {}", application.id(), e);
            return ResponseEntity.internalServerError().build();
        }

        try {
            staffApplicationDiscord.sendApplicationToDiscord(username, application);
        } catch (Exception e) {
            log.error("Failed to send staff application {} to Discord", application.id(), e);
            return ResponseEntity.internalServerError().build();
        }
        try {
            markAsSent(application.id());
        } catch (Exception e) {
            log.error("Failed to mark application {} as sent", application.id(), e);
            return ResponseEntity.internalServerError().build();
        }
        FormResponseDto response = buildResponse(application);
        return ResponseEntity.status(200).body(response);
    }

    private void saveApplication(StaffApplication application) {
        CompletableFuture<Void> saveFuture = new CompletableFuture<>();
        Connection.getConnection(Databases.DEFAULT)
                .runQuery(sqlSession -> {
                    try {
                        sqlSession.getMapper(StaffApplicationMapper.class).insert(application);
                        saveFuture.complete(null);
                    } catch (Exception e) {
                        log.error("Failed to insert staff application", e);
                        saveFuture.completeExceptionally(e);
                    }
                });
        saveFuture.join();
    }

    private Optional<EmailVerification> fetchEmailVerification(UUID userUuid, String email) {
        CompletableFuture<Optional<EmailVerification>> emailVerificationFuture = new CompletableFuture<>();
        Connection.getConnection(Databases.DEFAULT)
                .runQuery(sqlSession -> {
                    EmailVerification verifiedMail = sqlSession.getMapper(EmailVerificationMapper.class)
                            .findByUserAndEmail(userUuid, email);
                    emailVerificationFuture.complete(Optional.ofNullable(verifiedMail));
                });
        return emailVerificationFuture.join();
    }

    private void markAsSent(UUID applicationId) {
        Connection.getConnection(Databases.DEFAULT)
                .runQuery(sqlSession -> sqlSession.getMapper(StaffApplicationMapper.class).markAsSent(applicationId));
    }

    private FormResponseDto buildResponse(StaffApplication application) {
        String message = "Your staff application has been submitted. You will be notified when it has been reviewed.";
        return new FormResponseDto(
                application.id().toString(),
                message,
                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();
    }
}
