package com.arms.api.aichat.service;

import com.arms.api.aichat.domain.ChatMessage;
import com.arms.api.aichat.domain.ChatRoom;
import com.arms.api.aichat.domain.RecommendedCard;
import com.arms.api.aichat.dto.CardContextItem;
import com.arms.api.aichat.dto.LastSelectionRequest;
import com.arms.api.aichat.dto.RagDocsRequest;
import com.arms.api.util.redisrepo.RedisFramework;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import static com.arms.api.aichat.constant.AiChatRedisKeys.*;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.Comparator;

@Service
@RequiredArgsConstructor
@Slf4j
public class AiChatServiceImpl implements AiChatService {

    private final RedisFramework redisFramework;
    private final ObjectMapper objectMapper;


    @Override
    public ChatRoom createRoom(String userId, String title, String pdServiceId, List<String> pdServiceVersionIds) {
        String roomListKey = String.format(DEFAULT_ROOM_LIST, userId);
        deleteOldestRoomIfOverLimit(roomListKey);

        String roomId = UUID.randomUUID().toString();
        long now = System.currentTimeMillis();

        ChatRoom room = buildChatRoom(roomId, userId, title, pdServiceId, pdServiceVersionIds, now);
        saveRoomInfo(room, now);
        registerRoomToList(room, roomListKey, now);

        return room;
    }

    private ChatRoom buildChatRoom(String roomId, String userId, String title,
                                   String pdServiceId, List<String> pdServiceVersionIds, long now) {
        return ChatRoom.builder()
                .roomId(roomId)
                .userId(userId)
                .title(title != null ? title : "새 대화")
                .createdAt(now)
                .updatedAt(now)
                .pdServiceId(pdServiceId)
                .pdServiceVersionIds(pdServiceVersionIds)
                .build();
    }

    private void saveRoomInfo(ChatRoom room, long now) {
        String roomInfoKey = String.format(ROOM_INFO, room.getRoomId());

        Map<String, String> roomData = new HashMap<>();
        roomData.put("roomId", room.getRoomId());
        roomData.put("userId", room.getUserId());
        roomData.put("title", room.getTitle());
        roomData.put("createdAt", String.valueOf(now));
        roomData.put("updatedAt", String.valueOf(now));
        if (room.getPdServiceId() != null) {
            roomData.put("pdServiceId", room.getPdServiceId());
        }
        if (room.getPdServiceVersionIds() != null && !room.getPdServiceVersionIds().isEmpty()) {
            roomData.put("pdServiceVersionIds", String.join(",", room.getPdServiceVersionIds()));
        }

        redisFramework.hash().setAll(roomInfoKey, roomData);
        redisFramework.expire(roomInfoKey, TTL_DAYS, TimeUnit.DAYS);
    }

    private void registerRoomToList(ChatRoom room, String listKey, long now) {
        redisFramework.zSet().add(listKey, room.getRoomId(), now);
        redisFramework.expire(listKey, TTL_DAYS, TimeUnit.DAYS);
        log.info("Created chat room: {} in list: {}", room.getRoomId(), listKey);
    }

    @Override
    public ChatMessage saveMessage(String roomId, String role, String content) {
        String messageId = UUID.randomUUID().toString();
        long now = System.currentTimeMillis();

        ChatMessage message = buildMessage(messageId, roomId, role, content, now);
        appendMessageWithTrim(String.format(MESSAGE_LIST, roomId), message);
        refreshRoomUpdatedAt(roomId, now);

        return message;
    }

    private ChatMessage buildMessage(String messageId, String roomId, String role, String content, long now) {
        return ChatMessage.builder()
                .messageId(messageId)
                .roomId(roomId)
                .role(role)
                .content(content)
                .createdAt(now)
                .build();
    }

    @Override
    public void replaceLastAssistantMessage(String roomId, String content) {
        String messageListKey = String.format(MESSAGE_LIST, roomId);
        List<Object> messages = redisFramework.list().getRange(messageListKey, 0, -1);

        if (messages == null || messages.isEmpty()) {
            saveMessage(roomId, "assistant", content);
            return;
        }

        for (int i = messages.size() - 1; i >= 0; i--) {
            ChatMessage msg = (ChatMessage) messages.get(i);
            if ("assistant".equals(msg.getRole())) {
                long now = System.currentTimeMillis();
                ChatMessage updated = buildMessage(msg.getMessageId(), roomId, "assistant", content, now);
                redisFramework.list().set(messageListKey, i, updated);
                refreshRoomUpdatedAt(roomId, now);
                log.info("replaceLastAssistantMessage: roomId={}, index={}", roomId, i);
                return;
            }
        }

        // assistant 메시지가 없으면 새로 추가
        saveMessage(roomId, "assistant", content);
    }

    private void appendMessageWithTrim(String messageListKey, ChatMessage message) {
        Long size = redisFramework.list().addLast(messageListKey, message);
        redisFramework.expire(messageListKey, TTL_DAYS, TimeUnit.DAYS);

        if (size != null && size > MAX_MESSAGES) {
            redisFramework.list().trim(messageListKey, size - MAX_MESSAGES, -1);
        }
    }

    private void refreshRoomUpdatedAt(String roomId, long now) {
        String roomInfoKey = String.format(ROOM_INFO, roomId);

        Map<Object, Object> roomData = redisFramework.hash().getAll(roomInfoKey);

        String userId = (String) roomData.get("userId");

        redisFramework.hash().set(roomInfoKey, "updatedAt", String.valueOf(now));

        if (userId != null) {
            redisFramework.zSet().add(String.format(DEFAULT_ROOM_LIST, userId), roomId, now);
        }
    }

    @Override
    public List<ChatRoom> findRoomByUserId(String userId) {
        return findRoomsByKey(String.format(DEFAULT_ROOM_LIST, userId));
    }

    @Override
    public List<ChatMessage> findMessageByRoomId(String roomId) {
        String messageListKey = String.format(MESSAGE_LIST, roomId);

        List<Object> messages = redisFramework.list().getRange(messageListKey, 0, -1);

        if (messages == null || messages.isEmpty()) {
            return Collections.emptyList();
        }

        List<ChatMessage> result = new ArrayList<>();
        for (Object obj : messages) {
            ChatMessage message = (ChatMessage) obj;
            result.add(message);
        }
        return result;
    }

    @Override
    public void updateRoomPdService(String roomId, String pdServiceId, List<String> pdServiceVersionIds) {
        String roomInfoKey = String.format(ROOM_INFO, roomId);
        if (pdServiceId != null && !pdServiceId.isBlank()) {
            redisFramework.hash().set(roomInfoKey, "pdServiceId", pdServiceId);
        } else {
            redisFramework.hash().delete(roomInfoKey, "pdServiceId");
        }
        if (pdServiceVersionIds != null && !pdServiceVersionIds.isEmpty()) {
            redisFramework.hash().set(roomInfoKey, "pdServiceVersionIds", String.join(",", pdServiceVersionIds));
        } else {
            redisFramework.hash().delete(roomInfoKey, "pdServiceVersionIds");
        }
        log.info("Updated room pdService: roomId={}, pdServiceId={}", roomId, pdServiceId);
    }

    public void deleteRoom(String userId, String roomId) {
        deleteRoomData(roomId);
        redisFramework.zSet().remove(String.format(DEFAULT_ROOM_LIST, userId), roomId);
    }

    private void deleteRoomData(String roomId) {
        redisFramework.delete(List.of(
                String.format(MESSAGE_LIST, roomId),
                String.format(ROOM_INFO, roomId),
                String.format(ROOM_RAG_DOCS, roomId)
        ));
    }

    @Override
    public void saveLastSelection(String userId, String pdServiceId, List<String> pdServiceVersionIds) {
        String key = String.format(LAST_SELECTION, userId);

        // pdServiceId가 없으면 선택 자체가 없는 상태이므로 키 전체 삭제
        if (pdServiceId == null || pdServiceId.isBlank()) {
            redisFramework.delete(key);
            return;
        }

        Map<String, String> data = new HashMap<>();
        data.put("pdServiceId", pdServiceId);

        if (pdServiceVersionIds != null && !pdServiceVersionIds.isEmpty()) {
            data.put("pdServiceVersionIds", String.join(",", pdServiceVersionIds));
        } else {
            // 버전이 비어있으면 기존 필드 명시적 삭제
            redisFramework.hash().delete(key, "pdServiceVersionIds");
        }

        redisFramework.hash().setAll(key, data);
        redisFramework.expire(key, TTL_DAYS, TimeUnit.DAYS);
    }

    @Override
    public LastSelectionRequest findLastSelection(String userId) {
        String key = String.format(LAST_SELECTION, userId);
        Map<Object, Object> data = redisFramework.hash().getAll(key);

        if (data == null || data.isEmpty()) {
            return new LastSelectionRequest();
        }

        return LastSelectionRequest.builder()
                .pdServiceId((String) data.get("pdServiceId"))
                .pdServiceVersionIds(parseVersionIds((String) data.get("pdServiceVersionIds")))
                .build();
    }

    @Override
    public void deleteLastSelection(String userId) {
        String key = String.format(LAST_SELECTION, userId);
        redisFramework.delete(key);
    }

    private ChatRoom findRoomById(String roomId) {
        String roomInfoKey = String.format(ROOM_INFO, roomId);
        Map<Object, Object> data = redisFramework.hash().getAll(roomInfoKey);

        if (data == null || data.isEmpty()) {
            return null;
        }

        return ChatRoom.builder()
                .roomId((String) data.get("roomId"))
                .userId((String) data.get("userId"))
                .title((String) data.get("title"))
                .createdAt(Long.parseLong((String) data.get("createdAt")))
                .updatedAt(Long.parseLong((String) data.get("updatedAt")))
                .pdServiceId((String) data.get("pdServiceId"))
                .pdServiceVersionIds(parseVersionIds((String) data.get("pdServiceVersionIds")))
                .build();
    }

    private List<ChatRoom> findRoomsByKey(String listKey) {
        Set<Object> roomIds = redisFramework.zSet().reverseRange(listKey, 0, -1);

        if (roomIds == null || roomIds.isEmpty()) {
            return Collections.emptyList();
        }

        List<ChatRoom> rooms = new ArrayList<>();
        for (Object id : roomIds) {
            ChatRoom room = findRoomById((String) id);
            if (room != null) {
                rooms.add(room);
            }
        }
        return rooms;
    }

    // === 카드 컨텍스트 관련 ===

    @Override
    public List<CardContextItem> findCardContext(String userId) {
        Map<Object, Object> stored = redisFramework.hash().getAll(RECOMMENDED_CARDS);
        if (stored == null || stored.isEmpty()) {
            return Collections.emptyList();
        }
        List<RecommendedCard> cards = new ArrayList<>();
        for (Object v : stored.values()) {
            cards.add(objectMapper.convertValue(v, RecommendedCard.class));
        }
        cards.sort(Comparator.comparingInt(RecommendedCard::getSortOrder));
        List<CardContextItem> result = new ArrayList<>();
        for (RecommendedCard card : cards) {
            result.add(new CardContextItem(card.getQuestion()));
        }
        return result;
    }

    private String popOldestFromZSet(String listKey) {
        Set<Object> oldest = redisFramework.zSet().range(listKey, 0, 0);
        if (oldest == null || oldest.isEmpty()) return null;
        String oldId = (String) oldest.iterator().next();
        redisFramework.zSet().remove(listKey, oldId);
        return oldId;
    }

    private void deleteOldestRoomIfOverLimit(String listKey) {
        long count = redisFramework.zSet().size(listKey);
        while (count >= MAX_HISTORY) {
            String oldId = popOldestFromZSet(listKey);
            if (oldId == null) break;
            deleteRoomData(oldId);
            log.info("Deleted oldest room: {} from list: {}", oldId, listKey);
            count--;
        }
    }

    // === RAG 문서 관련 ===

    @Override
    public void saveRagDocs(String roomId, RagDocsRequest request) {
        String key = String.format(ROOM_RAG_DOCS, roomId);
        try {
            Map<String, String> data = new HashMap<>();
            data.put("wikiDocs", objectMapper.writeValueAsString(
                    request.getWikiDocs() != null ? request.getWikiDocs() : Collections.emptyList()));
            data.put("ragDocs", objectMapper.writeValueAsString(
                    request.getRagDocs() != null ? request.getRagDocs() : Collections.emptyList()));
            data.put("keyword", request.getKeyword() != null ? request.getKeyword() : "");
            if (request.getCheckedWikiIndexList() != null) {
                data.put("checkedWikiIndexList", objectMapper.writeValueAsString(request.getCheckedWikiIndexList()));
            }
            if (request.getCheckedRagIndexList() != null) {
                data.put("checkedRagIndexList", objectMapper.writeValueAsString(request.getCheckedRagIndexList()));
            }
            redisFramework.hash().setAll(key, data);
            redisFramework.expire(key, TTL_DAYS, TimeUnit.DAYS);
        } catch (Exception e) {
            log.error("Failed to save rag docs for room: {}", roomId, e);
        }
    }

    @Override
    public RagDocsRequest findRagDocs(String roomId) {
        String key = String.format(ROOM_RAG_DOCS, roomId);
        Map<Object, Object> data = redisFramework.hash().getAll(key);
        if (data == null || data.isEmpty()) {
            return null;
        }
        try {
            List<Object> wikiDocs = objectMapper.readValue(
                    (String) data.get("wikiDocs"), new TypeReference<List<Object>>() {});
            List<Object> ragDocs = objectMapper.readValue(
                    (String) data.get("ragDocs"), new TypeReference<List<Object>>() {});
            String keyword = (String) data.get("keyword");

            List<Integer> checkedWikiIndexList = null;
            List<Integer> checkedRagIndexList = null;
            String wikiIdx = (String) data.get("checkedWikiIndexList");
            String ragIdx  = (String) data.get("checkedRagIndexList");
            if (wikiIdx != null) {
                checkedWikiIndexList = objectMapper.readValue(wikiIdx, new TypeReference<List<Integer>>() {});
            }
            if (ragIdx != null) {
                checkedRagIndexList = objectMapper.readValue(ragIdx, new TypeReference<List<Integer>>() {});
            }

            return RagDocsRequest.builder()
                    .wikiDocs(wikiDocs)
                    .ragDocs(ragDocs)
                    .keyword(keyword)
                    .checkedWikiIndexList(checkedWikiIndexList)
                    .checkedRagIndexList(checkedRagIndexList)
                    .build();
        } catch (Exception e) {
            log.error("Failed to find rag docs for room: {}", roomId, e);
            return null;
        }
    }

    @Override
    public void deleteRagDocs(String roomId) {
        redisFramework.delete(String.format(ROOM_RAG_DOCS, roomId));
    }

    private List<String> parseVersionIds(String raw) {
        if (raw == null || raw.isEmpty()) {
            return null;
        }
        return Arrays.asList(raw.split(","));
    }

}
