package mk.finki.roomreservation.service;

import mk.finki.roomreservation.model.ActivateAccountRequest;
import mk.finki.roomreservation.model.LoginRequest;
import mk.finki.roomreservation.model.RegisterRequest;
import mk.finki.roomreservation.model.UserOption;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.regex.Pattern;

@Service
public class AuthService {

    private static final Pattern EMAIL_PATTERN =
            Pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");

    private final JdbcTemplate jdbcTemplate;
    private final BCryptPasswordEncoder passwordEncoder;

    public AuthService(JdbcTemplate jdbcTemplate, BCryptPasswordEncoder passwordEncoder) {
        this.jdbcTemplate = jdbcTemplate;
        this.passwordEncoder = passwordEncoder;
    }

    public UserOption login(LoginRequest request) {
        validateLoginRequest(request);

        UserOption user = findUserByIdentifier(request.identifier());
        String passwordHash = findPasswordHash(user.userId());

        if (!passwordEncoder.matches(request.password(), passwordHash)) {
            throw new IllegalArgumentException("Invalid username/email or password.");
        }

        return user;
    }

    @Transactional
    public UserOption register(RegisterRequest request) {
        validateRegisterRequest(request);

        String fullName = request.fullName().trim();
        String username = request.username().trim();
        String email = request.email().trim();

        if (usernameExists(username)) {
            throw new IllegalArgumentException("Username is already taken.");
        }

        if (emailExists(email)) {
            throw new IllegalArgumentException("Email is already registered.");
        }

        Integer userId = jdbcTemplate.queryForObject(
                """
                INSERT INTO project.users (
                    username,
                    email,
                    full_name,
                    role
                )
                VALUES (?, ?, ?, 'regular')
                RETURNING user_id
                """,
                Integer.class,
                username,
                email,
                fullName
        );

        if (userId == null) {
            throw new RuntimeException("User registration failed.");
        }

        String passwordHash = passwordEncoder.encode(request.password());

        jdbcTemplate.update(
                """
                INSERT INTO project.user_credentials (
                    user_id,
                    password_hash
                )
                VALUES (?, ?)
                """,
                userId,
                passwordHash
        );

        return findUserById(userId);
    }

    @Transactional
    public UserOption activateExistingAccount(ActivateAccountRequest request) {
        validateActivationRequest(request);

        UserOption user = findUserByIdentifier(request.identifier());

        if (credentialsExist(user.userId())) {
            throw new IllegalArgumentException("This account already has a password.");
        }

        String passwordHash = passwordEncoder.encode(request.password());

        jdbcTemplate.update(
                """
                INSERT INTO project.user_credentials (
                    user_id,
                    password_hash
                )
                VALUES (?, ?)
                """,
                user.userId(),
                passwordHash
        );

        return user;
    }

    private void validateLoginRequest(LoginRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("Login request is required.");
        }

        if (request.identifier() == null || request.identifier().isBlank()) {
            throw new IllegalArgumentException("Username or email is required.");
        }

        if (request.password() == null || request.password().isBlank()) {
            throw new IllegalArgumentException("Password is required.");
        }
    }

    private void validateRegisterRequest(RegisterRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("Register request is required.");
        }

        if (request.fullName() == null || request.fullName().isBlank()) {
            throw new IllegalArgumentException("Full name is required.");
        }

        if (request.username() == null || request.username().isBlank()) {
            throw new IllegalArgumentException("Username is required.");
        }

        if (request.email() == null || request.email().isBlank()) {
            throw new IllegalArgumentException("Email is required.");
        }

        if (!EMAIL_PATTERN.matcher(request.email().trim()).matches()) {
            throw new IllegalArgumentException("Email format is not valid.");
        }

        validatePassword(request.password());
    }

    private void validateActivationRequest(ActivateAccountRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("Activation request is required.");
        }

        if (request.identifier() == null || request.identifier().isBlank()) {
            throw new IllegalArgumentException("Username or email is required.");
        }

        validatePassword(request.password());
    }

    private void validatePassword(String password) {
        if (password == null || password.isBlank()) {
            throw new IllegalArgumentException("Password is required.");
        }

        if (password.length() < 8) {
            throw new IllegalArgumentException("Password must contain at least 8 characters.");
        }
    }

    private UserOption findUserByIdentifier(String identifier) {
        List<UserOption> users = jdbcTemplate.query(
                """
                SELECT
                    user_id,
                    username,
                    email,
                    full_name,
                    role
                FROM project.users
                WHERE LOWER(username) = LOWER(?)
                   OR LOWER(email) = LOWER(?)
                ORDER BY user_id
                LIMIT 1
                """,
                (rs, rowNum) -> new UserOption(
                        rs.getInt("user_id"),
                        rs.getString("username"),
                        rs.getString("email"),
                        rs.getString("full_name"),
                        rs.getString("role")
                ),
                identifier.trim(),
                identifier.trim()
        );

        if (users.isEmpty()) {
            throw new IllegalArgumentException("Invalid username/email or password.");
        }

        return users.get(0);
    }

    private UserOption findUserById(int userId) {
        return jdbcTemplate.queryForObject(
                """
                SELECT
                    user_id,
                    username,
                    email,
                    full_name,
                    role
                FROM project.users
                WHERE user_id = ?
                """,
                (rs, rowNum) -> new UserOption(
                        rs.getInt("user_id"),
                        rs.getString("username"),
                        rs.getString("email"),
                        rs.getString("full_name"),
                        rs.getString("role")
                ),
                userId
        );
    }

    private String findPasswordHash(int userId) {
        List<String> hashes = jdbcTemplate.query(
                """
                SELECT password_hash
                FROM project.user_credentials
                WHERE user_id = ?
                """,
                (rs, rowNum) -> rs.getString("password_hash"),
                userId
        );

        if (hashes.isEmpty()) {
            throw new IllegalArgumentException("This account is not activated yet. Please request access.");
        }

        return hashes.get(0);
    }

    private boolean usernameExists(String username) {
        Long count = jdbcTemplate.queryForObject(
                """
                SELECT COUNT(*)
                FROM project.users
                WHERE LOWER(username) = LOWER(?)
                """,
                Long.class,
                username
        );

        return count != null && count > 0;
    }

    private boolean emailExists(String email) {
        Long count = jdbcTemplate.queryForObject(
                """
                SELECT COUNT(*)
                FROM project.users
                WHERE LOWER(email) = LOWER(?)
                """,
                Long.class,
                email
        );

        return count != null && count > 0;
    }

    private boolean credentialsExist(int userId) {
        Long count = jdbcTemplate.queryForObject(
                """
                SELECT COUNT(*)
                FROM project.user_credentials
                WHERE user_id = ?
                """,
                Long.class,
                userId
        );

        return count != null && count > 0;
    }
}