package stirling.software.proprietary.security;

import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.enumeration.Role;
import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.service.DatabaseServiceInterface;
import stirling.software.proprietary.security.service.SaveUserRequest;
import stirling.software.proprietary.security.service.TeamService;
import stirling.software.proprietary.security.service.UserService;
import stirling.software.proprietary.service.UserLicenseSettingsService;

@Slf4j
@Component
@RequiredArgsConstructor
public class InitialSecuritySetup {

    @Value("${v2:false}")
    private boolean v2Enabled = false;

    private final UserService userService;
    private final TeamService teamService;
    private final ApplicationProperties applicationProperties;
    private final DatabaseServiceInterface databaseService;
    private final UserLicenseSettingsService licenseSettingsService;
    private final Environment environment;

    /**
     * SaaS manages identity in Supabase and billing via PAYG, so the self-host bootstrap steps that
     * scan/rewrite the whole user table (default-team backfill, seat-license grandfathering) don't
     * apply - and against a large SaaS user table they stall startup with full-table loads +
     * per-row saveAll. Per-user team assignment happens in SupabaseAuthenticationFilter instead.
     */
    private boolean isSaas() {
        return Arrays.asList(environment.getActiveProfiles()).contains("saas");
    }

    @PostConstruct
    public void init() {
        try {

            if (!userService.hasUsers()) {
                if (databaseService.hasBackup()) {
                    databaseService.importDatabase();
                } else {
                    initializeAdminUser();
                }
            }

            configureJWTSettings();
            initializeInternalApiUser();
            if (isSaas()) {
                log.info(
                        "SaaS profile active - skipping self-host user-table bootstrap"
                                + " (default-team backfill, seat-license grandfathering).");
            } else {
                assignUsersToDefaultTeamIfMissing();
                initializeUserLicenseSettings();
            }
        } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
            log.error("Failed to initialize security setup.", e);
            System.exit(1);
        }
    }

    private void initializeUserLicenseSettings() {
        licenseSettingsService.initializeGrandfatheredCount();
        licenseSettingsService.updateLicenseMaxUsers();
        licenseSettingsService.grandfatherExistingOAuthUsers();
    }

    private void configureJWTSettings() {
        ApplicationProperties.Security.Jwt jwtProperties =
                applicationProperties.getSecurity().getJwt();

        boolean jwtEnabled = jwtProperties.isEnableKeystore();
        if (!v2Enabled || !jwtEnabled) {
            log.debug(
                    "V2 enabled: {}, JWT enabled: {} - disabling all JWT features",
                    v2Enabled,
                    jwtEnabled);

            jwtProperties.setEnableKeyCleanup(false);
        }
    }

    private void assignUsersToDefaultTeamIfMissing() {
        Team defaultTeam = teamService.getOrCreateDefaultTeam();
        Team internalTeam = teamService.getOrCreateInternalTeam();
        List<User> usersWithoutTeam = userService.getUsersWithoutTeam();

        for (User user : usersWithoutTeam) {
            if (user.getUsername().equalsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
                user.setTeam(internalTeam);
            } else {
                user.setTeam(defaultTeam);
            }
        }

        userService.saveAll(usersWithoutTeam); // batch save
        if (usersWithoutTeam != null && !usersWithoutTeam.isEmpty()) {
            log.info(
                    "Assigned {} user(s) without a team to the default team.",
                    usersWithoutTeam.size());
        }
    }

    private void initializeAdminUser() throws SQLException, UnsupportedProviderException {
        String initialUsername =
                applicationProperties.getSecurity().getInitialLogin().getUsername();
        String initialPassword =
                applicationProperties.getSecurity().getInitialLogin().getPassword();
        if (initialUsername != null
                && !initialUsername.isEmpty()
                && initialPassword != null
                && !initialPassword.isEmpty()
                && userService.findByUsernameIgnoreCase(initialUsername).isEmpty()) {

            Team team = teamService.getOrCreateDefaultTeam();
            SaveUserRequest.Builder builder =
                    SaveUserRequest.builder()
                            .username(initialUsername)
                            .password(initialPassword)
                            .team(team)
                            .role(Role.ADMIN.getRoleId())
                            .firstLogin(false);
            userService.saveUserCore(builder.build());
            log.info("Admin user created: {}", initialUsername);
        } else {
            createDefaultAdminUser();
        }
    }

    private void createDefaultAdminUser() throws SQLException, UnsupportedProviderException {
        String defaultUsername = "admin";
        String defaultPassword = "stirling";

        if (userService.findByUsernameIgnoreCase(defaultUsername).isEmpty()) {
            Team team = teamService.getOrCreateDefaultTeam();
            SaveUserRequest.Builder builder =
                    SaveUserRequest.builder()
                            .username(defaultUsername)
                            .password(defaultPassword)
                            .team(team)
                            .role(Role.ADMIN.getRoleId())
                            .firstLogin(true);
            userService.saveUserCore(builder.build());
            log.info("Default admin user created: {}", defaultUsername);
        }
    }

    private void initializeInternalApiUser()
            throws IllegalArgumentException, SQLException, UnsupportedProviderException {
        if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
            Team team = teamService.getOrCreateInternalTeam();
            SaveUserRequest.Builder builder =
                    SaveUserRequest.builder()
                            .username(Role.INTERNAL_API_USER.getRoleId())
                            .password(UUID.randomUUID().toString())
                            .team(team)
                            .role(Role.INTERNAL_API_USER.getRoleId())
                            .firstLogin(false);
            userService.saveUserCore(builder.build());
            userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
            log.info("Internal API user created: {}", Role.INTERNAL_API_USER.getRoleId());
        } else {
            Optional<User> internalApiUserOpt =
                    userService.findByUsernameIgnoreCase(Role.INTERNAL_API_USER.getRoleId());
            if (internalApiUserOpt.isPresent()) {
                User internalApiUser = internalApiUserOpt.get();
                // move to team internal API user
                if (!TeamService.INTERNAL_TEAM_NAME.equals(internalApiUser.getTeam().getName())) {
                    log.info(
                            "Moving internal API user to team: {}", TeamService.INTERNAL_TEAM_NAME);
                    Team internalTeam = teamService.getOrCreateInternalTeam();

                    userService.changeUserTeam(internalApiUser, internalTeam);
                }
            }
        }
        userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
    }
}
