package com.petshop.backend.service;

import com.petshop.backend.dto.chat.ConversationRequest;
import com.petshop.backend.dto.chat.ConversationResponse;
import com.petshop.backend.dto.chat.MessageRequest;
import com.petshop.backend.dto.chat.MessageResponse;
import com.petshop.backend.dto.chat.UpdateConversationRequest;
import com.petshop.backend.entity.Conversation;
import com.petshop.backend.entity.Message;
import com.petshop.backend.entity.User;
import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.util.ContentFilter;
import com.petshop.backend.repository.ConversationRepository;
import com.petshop.backend.repository.MessageRepository;
import com.petshop.backend.repository.UserRepository;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class ChatService {
    private static final String BOT_USERNAME = "ai.bot";

    private final ConversationRepository conversationRepository;
    private final MessageRepository messageRepository;
    private final UserRepository userRepository;
    private final AvatarStorageService avatarStorageService;
    private final ChatAttachmentStorageService attachmentStorageService;
    private final EmailService emailService;

    public ChatService(ConversationRepository conversationRepository,
                      MessageRepository messageRepository,
                      UserRepository userRepository,
                      AvatarStorageService avatarStorageService,
                      ChatAttachmentStorageService attachmentStorageService,
                      EmailService emailService) {
        this.conversationRepository = conversationRepository;
        this.messageRepository = messageRepository;
        this.userRepository = userRepository;
        this.avatarStorageService = avatarStorageService;
        this.attachmentStorageService = attachmentStorageService;
        this.emailService = emailService;
    }

    @Transactional
    public ConversationResponse createConversation(Long userId, ConversationRequest request) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new ResourceNotFoundException("User not found"));

        Conversation conversation = new Conversation();
        conversation.setCustomerId(userId);
        conversation.setStatus(Conversation.ConversationStatus.OPEN);
        conversation.setMode(Conversation.ConversationMode.AUTOMATED);
        conversation = conversationRepository.save(conversation);

        Message message = new Message();
        message.setConversationId(conversation.getId());
        message.setSenderId(userId);
        message.setContent(request.getMessage());
        message.setIsRead(false);
        messageRepository.save(message);

        return ConversationResponse.fromEntity(conversation, request.getMessage(), userId);
    }

    public List<ConversationResponse> getConversations(Long userId, User.Role role, boolean mine) {
        List<Conversation> conversations;

        if (mine || role == User.Role.CUSTOMER) {
            conversations = conversationRepository.findByCustomerIdOrderByUpdatedAtDesc(userId);
        } else if (role == User.Role.STAFF) {
            List<Conversation> assignedToMe = conversationRepository.findByStaffIdOrderByUpdatedAtDesc(userId);
            List<Conversation> unassigned = conversationRepository.findByStaffIdIsNullOrderByUpdatedAtDesc();
            conversations = new java.util.ArrayList<>(assignedToMe);
            conversations.addAll(unassigned);
        } else {
            conversations = conversationRepository.findAllByOrderByUpdatedAtDesc();
        }

        return conversations.stream()
            .map(conv -> {
                List<Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conv.getId());
                Message last = messages.isEmpty() ? null : messages.get(messages.size() - 1);
                String lastMessage = last != null && last.getContent() != null ? last.getContent() : "";
                Long lastSenderId = last != null ? last.getSenderId() : null;
                return ConversationResponse.fromEntity(conv, lastMessage, lastSenderId);
            })
            .collect(Collectors.toList());
    }


    public Conversation getConversationEntity(Long id) {
        return conversationRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
    }

    public ConversationResponse getConversation(Long conversationId, Long userId, User.Role role) {
        Conversation conversation = conversationRepository.findById(conversationId)
            .orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));

        if (!hasConversationAccess(conversation, userId, role)) {
            if (role == User.Role.CUSTOMER) {
                throw new AccessDeniedException("You can only view your own conversations");
            }
            if (role == User.Role.STAFF) {
                throw new AccessDeniedException("You can only view conversations assigned to you or unassigned conversations");
            }
        }

        List<Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);
        Message last = messages.isEmpty() ? null : messages.get(messages.size() - 1);
        String lastMessage = last != null && last.getContent() != null ? last.getContent() : "";

        Long lastSenderId = last != null ? last.getSenderId() : null;
        return ConversationResponse.fromEntity(conversation, lastMessage, lastSenderId);
    }

    @Transactional
    public MessageResponse sendMessage(Long conversationId, Long userId, User.Role role, MessageRequest request) {
        Conversation conversation = conversationRepository.findById(conversationId)
            .orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));

        if (conversation.getStatus() == Conversation.ConversationStatus.CLOSED) {
            throw new AccessDeniedException("Conversation is closed");
        }

        if (!hasConversationAccess(conversation, userId, role)) {
            if (role == User.Role.CUSTOMER) {
                throw new AccessDeniedException("You can only send messages to your own conversations");
            }
            if (role == User.Role.STAFF) {
                throw new AccessDeniedException("You can only reply to conversations assigned to you or unassigned conversations");
            }
        }

        ContentFilter.validate(request.getContent());

        Message message = new Message();
        message.setConversationId(conversationId);
        message.setSenderId(userId);
        message.setContent(request.getContent());
        message.setAttachmentUrl(request.getAttachmentUrl());
        message.setAttachmentName(request.getAttachmentName());
        message.setAttachmentMimeType(request.getAttachmentMimeType());
        message.setAttachmentSizeBytes(request.getAttachmentSizeBytes());
        message.setIsRead(false);
        message = messageRepository.save(message);

        if (role == User.Role.STAFF && conversation.getStaffId() == null) {
            conversation.setStaffId(userId);
        }

        if (role == User.Role.STAFF) {
            conversation.setMode(Conversation.ConversationMode.HUMAN);
            conversationRepository.save(conversation);
        }

        return toMessageResponse(message);
    }


    @Transactional
    public MessageResponse saveBotMessage(Long conversationId, String content) {
        conversationRepository.findById(conversationId)
            .orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
        User botUser = getBotUser();

        Message message = new Message();
        message.setConversationId(conversationId);
        message.setSenderId(botUser.getId());
        message.setContent(content);
        message.setIsRead(false);
        message = messageRepository.save(message);

        return toMessageResponse(message);
    }

    @Transactional
    public MessageResponse sendMessageWithAttachment(Long conversationId, Long userId, User.Role role, MultipartFile file, String content) {
        Conversation conversation = conversationRepository.findById(conversationId)
                .orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));

        if (conversation.getStatus() == Conversation.ConversationStatus.CLOSED) {
            throw new AccessDeniedException("Conversation is closed");
        }

        if (!hasConversationAccess(conversation, userId, role)) {
            if (role == User.Role.CUSTOMER) {
                throw new AccessDeniedException("You can only send messages to your own conversations");
            }
            if (role == User.Role.STAFF) {
                throw new AccessDeniedException("You can only reply to conversations assigned to you or unassigned conversations");
            }
        }

        try {
            String attachmentUrl = attachmentStorageService.storeAttachment(file);
            Message message = new Message();
            message.setConversationId(conversationId);
            message.setSenderId(userId);
            message.setContent(content);
            message.setAttachmentUrl(attachmentUrl);
            message.setAttachmentName(file.getOriginalFilename());
            message.setAttachmentMimeType(file.getContentType());
            message.setAttachmentSizeBytes(file.getSize());
            message.setIsRead(false);
            message = messageRepository.save(message);

            if (role == User.Role.STAFF && conversation.getStaffId() == null) {
                conversation.setStaffId(userId);
            }

            if (role == User.Role.STAFF) {
                conversation.setMode(Conversation.ConversationMode.HUMAN);
                conversationRepository.save(conversation);
            }

            return toMessageResponse(message);
        } catch (IOException e) {
            throw new RuntimeException("Failed to store attachment", e);
        }
    }

    @Transactional
    public ConversationResponse requestHumanTakeover(Long conversationId, Long userId, User.Role role) {
        Conversation conversation = conversationRepository.findById(conversationId)
                .orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));

        if (conversation.getStatus() == Conversation.ConversationStatus.CLOSED) {
            throw new AccessDeniedException("Conversation is closed");
        }

        if (!hasConversationAccess(conversation, userId, role)) {
            throw new AccessDeniedException("You can only request human takeover for your own conversations");
        }

        if (conversation.getHumanRequestedAt() == null) {
            conversation.setHumanRequestedAt(LocalDateTime.now());
        }
        conversationRepository.save(conversation);

        List<Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);
        Message last = messages.isEmpty() ? null : messages.get(messages.size() - 1);
        String lastMessage = last != null && last.getContent() != null ? last.getContent() : "";
        Long lastSenderId = last != null ? last.getSenderId() : null;
        return ConversationResponse.fromEntity(conversation, lastMessage, lastSenderId);
    }

    @Transactional
    public ConversationResponse updateConversation(Long conversationId, Long userId, User.Role role, UpdateConversationRequest request) {
        Conversation conversation = conversationRepository.findById(conversationId)
                .orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));

        if (!hasConversationAccess(conversation, userId, role)) {
            if (role == User.Role.CUSTOMER) {
                throw new AccessDeniedException("You can only close your own conversations");
            }
            if (role == User.Role.STAFF) {
                throw new AccessDeniedException("You can only close conversations assigned to you or unassigned conversations");
            }
        }

        conversation.setStatus(Conversation.ConversationStatus.valueOf(request.getStatus()));
        Conversation savedConversation = conversationRepository.save(conversation);

        List<Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);

        if (Conversation.ConversationStatus.CLOSED.name().equals(request.getStatus())) {
            userRepository.findById(savedConversation.getCustomerId()).ifPresent(customer ->
                    emailService.sendChatTranscript(savedConversation, messages, customer));
        }

        Message last = messages.isEmpty() ? null : messages.get(messages.size() - 1);
        String lastMessage = last != null && last.getContent() != null ? last.getContent() : "";
        Long lastSenderId = last != null ? last.getSenderId() : null;
        return ConversationResponse.fromEntity(savedConversation, lastMessage, lastSenderId);
    }

    public List<MessageResponse> getMessages(Long conversationId, Long userId, User.Role role) {
        Conversation conversation = conversationRepository.findById(conversationId)
            .orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));

        if (!hasConversationAccess(conversation, userId, role)) {
            if (role == User.Role.CUSTOMER) {
                throw new AccessDeniedException("You can only view messages from your own conversations");
            }
            if (role == User.Role.STAFF) {
                throw new AccessDeniedException("You can only view messages from conversations assigned to you or unassigned conversations");
            }
        }

        List<Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);
        return messages.stream()
            .map(this::toMessageResponse)
            .collect(Collectors.toList());
    }

    private MessageResponse toMessageResponse(Message message) {
        MessageResponse response = MessageResponse.fromEntity(message);
        userRepository.findById(message.getSenderId())
            .ifPresentOrElse(
                user -> populateSenderMetadata(response, user),
                () -> {
                    response.setSenderRole("UNKNOWN");
                    response.setSenderDisplayName("Unknown");
                }
            );
        return response;
    }

    private void populateSenderMetadata(MessageResponse response, User user) {
        response.setSenderRole(resolveSenderRole(user));
        response.setSenderDisplayName(resolveSenderDisplayName(user));
        if (avatarStorageService.hasAvatar(user)) {
            response.setSenderAvatarUrl(avatarStorageService.toOwnerAvatarUrl(user));
        }
    }

    private String resolveSenderRole(User user) {
        if (BOT_USERNAME.equals(user.getUsername())) {
            return "BOT";
        }
        return user.getRole() != null ? user.getRole().name() : "UNKNOWN";
    }

    private String resolveSenderDisplayName(User user) {
        if (BOT_USERNAME.equals(user.getUsername())) {
            return "AI Bot";
        }
        if (user.getFullName() != null && !user.getFullName().isBlank()) {
            return user.getFullName();
        }
        StringBuilder displayName = new StringBuilder();
        if (user.getFirstName() != null && !user.getFirstName().isBlank()) {
            displayName.append(user.getFirstName().trim());
        }
        if (user.getLastName() != null && !user.getLastName().isBlank()) {
            if (!displayName.isEmpty()) {
                displayName.append(' ');
            }
            displayName.append(user.getLastName().trim());
        }
        if (!displayName.isEmpty()) {
            return displayName.toString();
        }
        return user.getUsername() != null && !user.getUsername().isBlank() ? user.getUsername() : "Unknown";
    }

    private User getBotUser() {
        return userRepository.findByUsername(BOT_USERNAME)
            .orElseThrow(() -> new ResourceNotFoundException("Bot user not found"));
    }

    public boolean hasConversationAccess(Long conversationId, Long userId, User.Role role) {
        Conversation conversation = conversationRepository.findById(conversationId)
                .orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
        return hasConversationAccess(conversation, userId, role);
    }

    private boolean hasConversationAccess(Conversation conversation, Long userId, User.Role role) {
        if (role == User.Role.ADMIN) {
            return true;
        }

        if (role == User.Role.CUSTOMER) {
            return conversation.getCustomerId().equals(userId);
        }

        if (role == User.Role.STAFF) {
            return conversation.getStaffId() == null || conversation.getStaffId().equals(userId);
        }

        return false;
    }
}
