package com.emonster.taroaichat.service;

import com.emonster.taroaichat.domain.User;
import com.emonster.taroaichat.domain.UserProfile;
import com.emonster.taroaichat.repository.UserProfileRepository;
import com.emonster.taroaichat.repository.UserRepository;
import com.emonster.taroaichat.security.jwt.TokenProvider;
import com.emonster.taroaichat.web.rest.vm.PhoneAuthenticationVM;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.Collections;
import java.util.Optional;

/**
 * Service for handling phone-based authentication
 */
@Service
@Transactional
public class PhoneAuthenticationService {

    private static final Logger log = LoggerFactory.getLogger(PhoneAuthenticationService.class);

    private final TwilioService twilioService;
    private final UserRepository userRepository;
    private final UserProfileRepository userProfileRepository;
    private final TokenProvider tokenProvider;
    private final UserService userService;

    public PhoneAuthenticationService(
        TwilioService twilioService,
        UserRepository userRepository,
        UserProfileRepository userProfileRepository,
        TokenProvider tokenProvider,
        UserService userService
    ) {
        this.twilioService = twilioService;
        this.userRepository = userRepository;
        this.userProfileRepository = userProfileRepository;
        this.tokenProvider = tokenProvider;
        this.userService = userService;
    }

    /**
     * Send verification code to phone number
     * @param phoneNumber The phone number to verify
     * @return true if code was sent successfully
     */
    public boolean sendVerificationCode(String phoneNumber) {
        String formattedPhone = twilioService.formatPhoneNumber(phoneNumber);
        return twilioService.sendVerificationCode(formattedPhone);
    }

    /**
     * Verify phone number and authenticate user with detailed profile info
     * @param phoneNumber The phone number
     * @param code The verification code
     * @param rememberMe Whether to create a longer-lasting token
     * @return Authentication result with user profile info
     */
    public Optional<PhoneAuthenticationVM> verifyAndAuthenticateWithProfile(String phoneNumber, String code, boolean rememberMe) {
        String formattedPhone = twilioService.formatPhoneNumber(phoneNumber);

        // Verify the code with Twilio
        if (!twilioService.checkVerificationCode(formattedPhone, code)) {
            log.warn("Invalid verification code for phone: {}", formattedPhone);
            return Optional.empty();
        }

        // Check if user already exists
        boolean isNewUser = userProfileRepository.findByPhone(formattedPhone).isEmpty();

        // Find or create user
        User user = findOrCreateUserByPhone(formattedPhone);

        // Get user profile (should exist after findOrCreateUserByPhone)
        UserProfile profile = userProfileRepository.findByPhone(formattedPhone).orElse(null);

        // Create authentication token
        Authentication authentication = createAuthentication(user);

        // Generate JWT tokens
        String accessToken = tokenProvider.createAccessToken(authentication, rememberMe);
        String refreshToken = tokenProvider.createRefreshToken(authentication);

        return Optional.of(new PhoneAuthenticationVM(
            accessToken,
            refreshToken,
            user,
            profile,
            isNewUser
        ));
    }

    /**
     * Find existing user by phone or create new one
     */
    private User findOrCreateUserByPhone(String phoneNumber) {
        // First check if user profile exists with this phone
        Optional<UserProfile> existingProfile = userProfileRepository.findByPhone(phoneNumber);

        if (existingProfile.isPresent() && existingProfile.get().getUser() != null) {
            User user = existingProfile.get().getUser();
            // Update last active
            existingProfile.get().setLastActive(Instant.now());
            userProfileRepository.save(existingProfile.get());
            return user;
        }

        // Create new user
        String login = generateLoginFromPhone(phoneNumber);
        User newUser = new User();
        newUser.setLogin(login);
        newUser.setActivated(true); // Auto-activate phone-verified users
        newUser.setLangKey("en");

        // Set a random password since we're using phone auth
        newUser.setPassword(userService.encodePassword(generateRandomPassword()));

        User savedUser = userRepository.save(newUser);

        // Create user profile
        UserProfile profile = new UserProfile();
        profile.setUser(savedUser);
        profile.setPhone(phoneNumber);
        profile.setLastActive(Instant.now());
        userProfileRepository.save(profile);

        log.info("Created new user with phone: {}", phoneNumber);
        return savedUser;
    }

    /**
     * Generate a unique login from phone number
     */
    private String generateLoginFromPhone(String phoneNumber) {
        // Remove + and use last 10 digits
        String cleanPhone = phoneNumber.replaceAll("[^0-9]", "");
        String base = "user_" + (cleanPhone.length() > 10 ?
            cleanPhone.substring(cleanPhone.length() - 10) : cleanPhone);

        // Check if login exists and add suffix if needed
        String login = base;
        int suffix = 1;
        while (userRepository.findOneByLogin(login).isPresent()) {
            login = base + "_" + suffix++;
        }

        return login;
    }

    /**
     * Generate a random password for phone-authenticated users
     */
    private String generateRandomPassword() {
        return java.util.UUID.randomUUID().toString();
    }

    /**
     * Create authentication object for user
     */
    private Authentication createAuthentication(User user) {
        return new UsernamePasswordAuthenticationToken(
            user.getLogin(),
            null,
            Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
        );
    }
}
