/*
 * Handles user login, registration, password reset, and profile updates.
 *
 * Author: Harkamal
 * Date: April 2026
 */
package com.petshop.backend.controller;

import com.petshop.backend.dto.auth.AvatarUploadResponse;
import com.petshop.backend.dto.auth.ForgotPasswordRequest;
import com.petshop.backend.dto.auth.ForgotPasswordResponse;
import com.petshop.backend.dto.auth.LoginRequest;
import com.petshop.backend.dto.auth.LoginResponse;
import com.petshop.backend.dto.auth.ProfileUpdateRequest;
import com.petshop.backend.dto.auth.RegisterRequest;
import com.petshop.backend.dto.auth.RegisterResponse;
import com.petshop.backend.dto.auth.ResetPasswordRequest;
import com.petshop.backend.dto.auth.ResetPasswordResponse;
import com.petshop.backend.dto.auth.UserInfoResponse;
import com.petshop.backend.entity.StoreLocation;
import com.petshop.backend.entity.User;
import com.petshop.backend.exception.BusinessException;
import com.petshop.backend.exception.ConflictException;
import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.security.JwtUtil;
import com.petshop.backend.security.UserAuthCacheService;
import com.petshop.backend.service.ActivityLogService;
import com.petshop.backend.service.AvatarStorageService;
import com.petshop.backend.service.EmailService;
import com.petshop.backend.service.PasswordResetService;
import com.petshop.backend.util.AuthenticationHelper;
import com.petshop.backend.util.ImageValidationUtil;
import com.petshop.backend.util.StringUtils;
import jakarta.validation.Valid;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {

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

    private final AuthenticationManager authenticationManager;
    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;
    private final PasswordEncoder passwordEncoder;
    private final AvatarStorageService avatarStorageService;
    private final ActivityLogService activityLogService;
    private final PasswordResetService passwordResetService;
    private final EmailService emailService;
    private final UserAuthCacheService userAuthCacheService;
    private final AuthenticationHelper authHelper;

    public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, AvatarStorageService avatarStorageService, ActivityLogService activityLogService, PasswordResetService passwordResetService, EmailService emailService, UserAuthCacheService userAuthCacheService, AuthenticationHelper authHelper) {
        this.authenticationManager = authenticationManager;
        this.userRepository = userRepository;
        this.jwtUtil = jwtUtil;
        this.passwordEncoder = passwordEncoder;
        this.avatarStorageService = avatarStorageService;
        this.activityLogService = activityLogService;
        this.passwordResetService = passwordResetService;
        this.emailService = emailService;
        this.userAuthCacheService = userAuthCacheService;
        this.authHelper = authHelper;
    }

    @PostMapping("/register")
    public ResponseEntity<RegisterResponse> register(@Valid @RequestBody RegisterRequest request) {
        String username = StringUtils.trimToNull(request.getUsername());
        String email = StringUtils.trimToNull(request.getEmail());
        String firstName = StringUtils.trimToNull(request.getFirstName());
        String lastName = StringUtils.trimToNull(request.getLastName());
        String phone = StringUtils.normalizePhone(request.getPhone());

        if (userRepository.findByUsername(username).isPresent()) {
            log.warn("Registration rejected: username already exists ({})", username);
            throw new ConflictException("Username already exists");
        }

        if (userRepository.findByEmail(email).isPresent()) {
            log.warn("Registration rejected: email already exists");
            throw new ConflictException("Email already exists");
        }

        if (phone != null && userRepository.findByPhone(phone).isPresent()) {
            log.warn("Registration rejected: phone already exists");
            throw new ConflictException("Phone already exists");
        }

        User user = new User();
        user.setUsername(username);
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        user.setEmail(email);
        user.setFirstName(firstName);
        user.setLastName(lastName);
        user.setFullName(StringUtils.fullName(firstName, lastName));
        user.setPhone(phone);
        user.setRole(User.Role.CUSTOMER);
        user.setActive(true);

        User savedUser;
        try {
            savedUser = userRepository.save(user);
        } catch (DataIntegrityViolationException e) {
            throw new ConflictException("Username, email, or phone already exists");
        }

        emailService.sendWelcome(savedUser);

        String token = jwtUtil.generateToken(savedUser);

        return ResponseEntity.status(HttpStatus.CREATED).body(new RegisterResponse(
                savedUser.getId(),
                savedUser.getUsername(),
                savedUser.getEmail(),
                savedUser.getPhone(),
                savedUser.getRole().name(),
                token
        ));
    }

    @PostMapping("/login")
    public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
            );

            User user = userRepository.findByUsername(request.getUsername())
                    .orElseThrow(() -> new UsernameNotFoundException("User not found"));

            String token = jwtUtil.generateToken(user);
            if (user.getRole() != User.Role.CUSTOMER) {
                activityLogService.record(user.getId(), "Logged in  |  POST /api/v1/auth/login → 200");
            }

            return ResponseEntity.ok(new LoginResponse(
                    token,
                    user.getUsername(),
                    user.getRole().name()
            ));

        } catch (BadCredentialsException e) {
            log.warn("Login failed for username {}", request.getUsername());
            throw e;
        } catch (InternalAuthenticationServiceException e) {
            if (e.getCause() instanceof DisabledException disabledException) {
                log.warn("Login denied for disabled user {}", request.getUsername());
                throw disabledException;
            }
            throw e;
        } catch (DisabledException e) {
            throw e;
        }
    }

    @PostMapping("/forgot-password")
    public ResponseEntity<ForgotPasswordResponse> forgotPassword(@Valid @RequestBody ForgotPasswordRequest request) {
        return ResponseEntity.ok(passwordResetService.createResetToken(request.getUsernameOrEmail()));
    }

    @PostMapping("/reset-password")
    public ResponseEntity<ResetPasswordResponse> resetPassword(@Valid @RequestBody ResetPasswordRequest request) {
        return ResponseEntity.ok(passwordResetService.resetPassword(request.getToken(), request.getNewPassword()));
    }

    @Transactional(readOnly = true)
    @GetMapping("/me")
    public ResponseEntity<UserInfoResponse> getCurrentUser() {
        User user = authHelper.getAuthenticatedUser();
        return ResponseEntity.ok(toUserInfoResponse(user));
    }

    @Transactional
    @PutMapping("/me")
    public ResponseEntity<UserInfoResponse> updateProfile(@Valid @RequestBody ProfileUpdateRequest request) {
        Long userId = AuthenticationHelper.getAuthenticatedUserId();
        User user = userRepository.findByIdForUpdate(userId)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));
        boolean invalidateToken = false;

        String username = StringUtils.trimToNull(request.getUsername());
        if (username != null && !username.equals(user.getUsername())) {
            if (userRepository.findByUsername(username).isPresent()) {
                throw new ConflictException("Username already exists");
            }
            user.setUsername(username);
            invalidateToken = true;
        }

        String email = StringUtils.trimToNull(request.getEmail());
        if (email != null && !email.equals(user.getEmail())) {
            if (userRepository.findByEmail(email).isPresent()) {
                throw new ConflictException("Email already exists");
            }
            user.setEmail(email);
        }

        String firstName = StringUtils.trimToNull(request.getFirstName());
        if (firstName != null) {
            user.setFirstName(firstName);
        }
        String lastName = StringUtils.trimToNull(request.getLastName());
        if (lastName != null) {
            user.setLastName(lastName);
        }
        if (firstName != null || lastName != null) {
            user.setFullName(StringUtils.fullName(user.getFirstName(), user.getLastName()));
        }

        if (request.getPhone() != null) {
            String phone = StringUtils.normalizePhone(request.getPhone());
            if (!java.util.Objects.equals(phone, user.getPhone())) {
                if (phone != null && userRepository.findByPhone(phone)
                        .filter(existing -> !existing.getId().equals(user.getId()))
                        .isPresent()) {
                    throw new ConflictException("Phone already exists");
                }
                user.setPhone(phone);
            }
        }

        if (request.getPassword() != null && !request.getPassword().isEmpty()) {
            user.setPassword(passwordEncoder.encode(request.getPassword()));
            invalidateToken = true;
        }

        if (invalidateToken) {
            user.setTokenVersion(user.getTokenVersion() + 1);
        }

        User updatedUser;
        try {
            updatedUser = userRepository.save(user);
        } catch (DataIntegrityViolationException e) {
            throw new ConflictException("Username, email, or phone already exists");
        }
        userAuthCacheService.evict(updatedUser.getId());
        return ResponseEntity.ok(toUserInfoResponse(updatedUser));
    }

    private UserInfoResponse toUserInfoResponse(User user) {
        StoreLocation primaryStore = user.getPrimaryStore();
        Long customerId = user.getRole() == User.Role.CUSTOMER ? user.getId() : null;
        String fullName = user.getFullName();
        if (fullName == null || fullName.isBlank()) {
            fullName = StringUtils.fullName(user.getFirstName(), user.getLastName());
        }
        return new UserInfoResponse(
                user.getId(),
                user.getUsername(),
                user.getFirstName(),
                user.getLastName(),
                user.getEmail(),
                fullName,
                user.getPhone(),
                avatarStorageService.toOwnerAvatarUrl(user),
                user.getRole().name(),
                customerId,
                primaryStore != null ? primaryStore.getStoreId() : null,
                primaryStore != null ? primaryStore.getStoreName() : null,
                user.getLoyaltyPoints()
        );
    }

    @PostMapping("/me/avatar")
    public ResponseEntity<AvatarUploadResponse> uploadAvatar(@RequestParam("avatar") MultipartFile file) throws IOException {
        User user = authHelper.getAuthenticatedUser();

        ImageValidationUtil.validate(file);

        avatarStorageService.deleteAvatar(user);
        String avatarPath = avatarStorageService.storeAvatar(file);
        user.setAvatarUrl(avatarPath);
        userRepository.save(user);

        return ResponseEntity.ok(new AvatarUploadResponse(avatarStorageService.toOwnerAvatarUrl(user), "Avatar uploaded successfully"));
    }

    @GetMapping("/me/avatar")
    public ResponseEntity<?> getAvatar() {
        User user = authHelper.getAuthenticatedUser();

        if (!avatarStorageService.hasAvatar(user)) {
            throw new ResourceNotFoundException("No avatar uploaded");
        }

        Map<String, String> response = new HashMap<>();
        response.put("avatarUrl", avatarStorageService.toOwnerAvatarUrl(user));
        return ResponseEntity.ok(response);
    }

    @GetMapping("/me/avatar/file")
    public ResponseEntity<Resource> getAvatarFile() {
        User user = authHelper.getAuthenticatedUser();

        if (avatarStorageService.hasAvatar(user)) {
            try {
                Resource resource = avatarStorageService.loadAvatarResource(user);
                MediaType mediaType = avatarStorageService.resolveMediaType(user);
                return ResponseEntity.ok().contentType(mediaType).body(resource);
            } catch (IllegalArgumentException ignored) {
            }
        }

        return ResponseEntity.ok()
                .contentType(MediaType.valueOf("image/svg+xml"))
                .body(avatarStorageService.loadDefaultAvatarResource());
    }

    @DeleteMapping("/me/avatar")
    public ResponseEntity<?> deleteAvatar() {
        User user = authHelper.getAuthenticatedUser();

        if (avatarStorageService.hasAvatar(user)) {
            try {
                avatarStorageService.deleteAvatar(user);
            } catch (IOException e) {
                log.warn("Failed to delete avatar for user {}: {}", user.getId(), e.getMessage());
            }
            user.setAvatarUrl(null);
            userRepository.save(user);
        }

        Map<String, String> response = new HashMap<>();
        response.put("message", "Avatar deleted successfully");
        return ResponseEntity.ok(response);
    }

    @PostMapping("/logout")
    public ResponseEntity<?> logout() {
        User user = authHelper.getAuthenticatedUser();
        user.setTokenVersion(user.getTokenVersion() + 1);
        userRepository.save(user);
        userAuthCacheService.evict(user.getId());
        Map<String, String> response = new HashMap<>();
        response.put("message", "Logged out successfully");
        return ResponseEntity.ok(response);
    }

}
