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.repository.UserRepository;
import com.petshop.backend.security.JwtUtil;
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.PhoneUtils;
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;

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

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

        if (userRepository.findByUsername(username).isPresent()) {
            log.warn("Registration rejected: username already exists ({})", username);
            Map<String, String> error = new HashMap<>();
            error.put("message", "Username already exists");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
        }

        if (userRepository.findByEmail(email).isPresent()) {
            log.warn("Registration rejected: email already exists");
            Map<String, String> error = new HashMap<>();
            error.put("message", "Email already exists");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
        }

        if (phone != null && userRepository.findByPhone(phone).isPresent()) {
            log.warn("Registration rejected: phone already exists");
            Map<String, String> error = new HashMap<>();
            error.put("message", "Phone already exists");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
        }

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

        User savedUser;
        try {
            savedUser = userRepository.save(user);
        } catch (DataIntegrityViolationException e) {
            Map<String, String> error = new HashMap<>();
            error.put("message", "Username, email, or phone already exists");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
        }

        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<?> 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());
            Map<String, String> error = new HashMap<>();
            error.put("message", "Invalid username or password");
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
        } catch (InternalAuthenticationServiceException e) {
            if (e.getCause() instanceof DisabledException disabledException) {
                log.warn("Login denied for disabled user {}", request.getUsername());
                Map<String, String> error = new HashMap<>();
                error.put("message", disabledException.getMessage());
                return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
            }
            throw e;
        } catch (DisabledException e) {
            Map<String, String> error = new HashMap<>();
            error.put("message", e.getMessage());
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
        }
    }

    @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 = getAuthenticatedUser();
        return ResponseEntity.ok(toUserInfoResponse(user));
    }

    @Transactional
    @PutMapping("/me")
    public ResponseEntity<?> 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 = trimToNull(request.getUsername());
        if (username != null && !username.equals(user.getUsername())) {
            if (userRepository.findByUsername(username).isPresent()) {
                Map<String, String> error = new HashMap<>();
                error.put("message", "Username already exists");
                return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
            }
            user.setUsername(username);
            invalidateToken = true;
        }

        String email = trimToNull(request.getEmail());
        if (email != null && !email.equals(user.getEmail())) {
            if (userRepository.findByEmail(email).isPresent()) {
                Map<String, String> error = new HashMap<>();
                error.put("message", "Email already exists");
                return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
            }
            user.setEmail(email);
        }

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

        if (request.getPhone() != null) {
            String phone = 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()) {
                    Map<String, String> error = new HashMap<>();
                    error.put("message", "Phone already exists");
                    return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
                }
                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) {
            Map<String, String> error = new HashMap<>();
            error.put("message", "Username, email, or phone already exists");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
        }
        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 = joinFullName(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()
        );
    }

    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    private String normalizePhone(String value) {
        return trimToNull(PhoneUtils.normalize(trimToNull(value)));
    }

    private NameParts splitFullName(String value) {
        String normalized = trimToNull(value);
        if (normalized == null) {
            throw new IllegalArgumentException("Full name is required");
        }
        String[] parts = normalized.split("\\s+", 2);
        String firstName = parts[0];
        String lastName = parts.length > 1 ? parts[1] : "";
        return new NameParts(firstName, lastName, joinFullName(firstName, lastName));
    }

    private String joinFullName(String firstName, String lastName) {
        String first = trimToNull(firstName);
        String last = trimToNull(lastName);
        if (first == null) {
            return last == null ? null : last;
        }
        if (last == null) {
            return first;
        }
        return first + " " + last;
    }

    private record NameParts(String firstName, String lastName, String fullName) {
    }

    @PostMapping("/me/avatar")
    public ResponseEntity<?> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
        User user = getAuthenticatedUser();

        if (file.isEmpty()) {
            Map<String, String> error = new HashMap<>();
            error.put("message", "Please select a file to upload");
            return ResponseEntity.badRequest().body(error);
        }

        if (file.getSize() > 5 * 1024 * 1024) {
            Map<String, String> error = new HashMap<>();
            error.put("message", "File size must not exceed 5MB");
            return ResponseEntity.badRequest().body(error);
        }

        String contentType = file.getContentType();
        if (contentType == null || (!contentType.equals("image/jpeg") && !contentType.equals("image/png") && !contentType.equals("image/gif"))) {
            Map<String, String> error = new HashMap<>();
            error.put("message", "Only JPG, PNG, and GIF images are allowed");
            return ResponseEntity.badRequest().body(error);
        }

        try {
            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"));

        } catch (IOException e) {
            Map<String, String> error = new HashMap<>();
            error.put("message", "Failed to upload avatar: " + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
        }
    }

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

        if (!avatarStorageService.hasAvatar(user)) {
            Map<String, String> error = new HashMap<>();
            error.put("message", "No avatar uploaded");
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
        }

        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 = getAuthenticatedUser();

        if (!avatarStorageService.hasAvatar(user)) {
            return ResponseEntity.notFound().build();
        }

        try {
            Resource resource = avatarStorageService.loadAvatarResource(user);
            MediaType mediaType = avatarStorageService.resolveMediaType(user);
            return ResponseEntity.ok().contentType(mediaType).body(resource);
        } catch (IllegalArgumentException ex) {
            return ResponseEntity.notFound().build();
        }
    }

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

        if (avatarStorageService.hasAvatar(user)) {
            try {
                avatarStorageService.deleteAvatar(user);
            } catch (IOException e) {
            }
            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() {
        Map<String, String> response = new HashMap<>();
        response.put("message", "Logged out successfully");
        response.put("note", "Token remains valid until expiration. Clear token from client storage.");
        return ResponseEntity.ok(response);
    }

    private User getAuthenticatedUser() {
        try {
            return AuthenticationHelper.getAuthenticatedUser(userRepository);
        } catch (RuntimeException ex) {
            throw new UsernameNotFoundException(ex.getMessage(), ex);
        }
    }
}
