package com.storycove.service;

import com.storycove.dto.CollectionDto;
import com.storycove.dto.SearchResultDto;
import com.storycove.dto.StoryReadingDto;
import com.storycove.dto.TagDto;
import com.storycove.entity.Collection;
import com.storycove.entity.CollectionStory;
import com.storycove.entity.Story;
import com.storycove.entity.Tag;
import com.storycove.repository.CollectionRepository;
import com.storycove.repository.CollectionStoryRepository;
import com.storycove.repository.SeriesRepository;
import com.storycove.repository.StoryRepository;
import com.storycove.repository.TagRepository;
import com.storycove.service.exception.ResourceNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class CollectionService {
    
    private static final Logger logger = LoggerFactory.getLogger(CollectionService.class);
    
    private final CollectionRepository collectionRepository;
    private final CollectionStoryRepository collectionStoryRepository;
    private final StoryRepository storyRepository;
    private final TagRepository tagRepository;
    private final SeriesRepository seriesRepository;
    private final SearchServiceAdapter searchServiceAdapter;
    private final ReadingTimeService readingTimeService;

    @Autowired
    public CollectionService(CollectionRepository collectionRepository,
                           CollectionStoryRepository collectionStoryRepository,
                           StoryRepository storyRepository,
                           TagRepository tagRepository,
                           SeriesRepository seriesRepository,
                           SearchServiceAdapter searchServiceAdapter,
                           ReadingTimeService readingTimeService) {
        this.collectionRepository = collectionRepository;
        this.collectionStoryRepository = collectionStoryRepository;
        this.storyRepository = storyRepository;
        this.tagRepository = tagRepository;
        this.seriesRepository = seriesRepository;
        this.searchServiceAdapter = searchServiceAdapter;
        this.readingTimeService = readingTimeService;
    }
    
    /**
     * Search collections using Solr (MANDATORY for all search/filter operations)
     * This method MUST be used instead of JPA queries for listing collections
     */
    public SearchResultDto<Collection> searchCollections(String query, List<String> tags, boolean includeArchived, int page, int limit) {
        try {
            // Use SearchServiceAdapter to search collections
            SearchResultDto<CollectionDto> searchResult = searchServiceAdapter.searchCollections(query, tags, includeArchived, page, limit);

            // Convert CollectionDto back to Collection entities by fetching from database
            List<Collection> collections = new ArrayList<>();
            for (CollectionDto dto : searchResult.getResults()) {
                try {
                    Collection collection = findByIdBasic(dto.getId());
                    collections.add(collection);
                } catch (ResourceNotFoundException e) {
                    logger.warn("Collection {} found in search index but not in database", dto.getId());
                }
            }

            return new SearchResultDto<>(collections, (int) searchResult.getTotalHits(), page, limit,
                query != null ? query : "", searchResult.getSearchTimeMs());
        } catch (Exception e) {
            logger.error("Collection search failed, falling back to empty results", e);
            return new SearchResultDto<>(new ArrayList<>(), 0, page, limit, query != null ? query : "", 0);
        }
    }
    
    /**
     * Find collection by ID with full details
     */
    public Collection findById(UUID id) {
        return collectionRepository.findByIdWithStoriesAndTags(id)
            .orElseThrow(() -> new ResourceNotFoundException("Collection not found with id: " + id));
    }
    
    /**
     * Find collection by ID with basic info only
     */
    public Collection findByIdBasic(UUID id) {
        return collectionRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Collection not found with id: " + id));
    }
    
    /**
     * Find all collections with tags for reindexing
     */
    public List<Collection> findAllWithTags() {
        return collectionRepository.findAllWithTags();
    }
    
    /**
     * Create a new collection with optional initial stories
     */
    public Collection createCollection(String name, String description, List<String> tagNames, List<UUID> initialStoryIds) {
        Collection collection = new Collection(name, description);
        
        // Add tags if provided
        if (tagNames != null && !tagNames.isEmpty()) {
            Set<Tag> tags = findOrCreateTags(tagNames);
            collection.setTags(tags);
        }
        
        Collection savedCollection = collectionRepository.save(collection);
        
        // Add initial stories if provided
        if (initialStoryIds != null && !initialStoryIds.isEmpty()) {
            addStoriesToCollection(savedCollection.getId(), initialStoryIds, null);
            // Reload to get updated collection with stories
            savedCollection = findById(savedCollection.getId());
        }
        
        searchServiceAdapter.indexCollection(savedCollection);

        logger.info("Created collection: {} with {} stories", name, initialStoryIds != null ? initialStoryIds.size() : 0);
        return savedCollection;
    }
    
    /**
     * Update collection metadata
     */
    public Collection updateCollection(UUID id, String name, String description, List<String> tagNames, Integer rating) {
        Collection collection = findByIdBasic(id);
        
        if (name != null) {
            collection.setName(name);
        }
        if (description != null) {
            collection.setDescription(description);
        }
        if (rating != null) {
            collection.setRating(rating);
        }
        
        // Update tags if provided
        if (tagNames != null) {
            Set<Tag> tags = findOrCreateTags(tagNames);
            collection.setTags(tags);
        }
        
        Collection savedCollection = collectionRepository.save(collection);
        searchServiceAdapter.updateCollection(findById(savedCollection.getId()));

        logger.info("Updated collection: {}", id);
        return savedCollection;
    }
    
    /**
     * Delete a collection (stories remain in the system)
     */
    public void deleteCollection(UUID id) {
        Collection collection = findByIdBasic(id);
        searchServiceAdapter.deleteCollection(id);
        collectionRepository.delete(collection);
        logger.info("Deleted collection: {}", id);
    }
    
    /**
     * Archive or unarchive a collection
     */
    public Collection archiveCollection(UUID id, boolean archived) {
        Collection collection = findByIdBasic(id);
        collection.setIsArchived(archived);
        
        Collection savedCollection = collectionRepository.save(collection);
        searchServiceAdapter.updateCollection(findById(savedCollection.getId()));

        logger.info("{} collection: {}", archived ? "Archived" : "Unarchived", id);
        return savedCollection;
    }
    
    /**
     * Add stories to a collection
     */
    public Map<String, Object> addStoriesToCollection(UUID collectionId, List<UUID> storyIds, Integer startPosition) {
        Collection collection = findByIdBasic(collectionId);
        
        // Validate stories exist
        List<Story> stories = storyRepository.findAllById(storyIds);
        if (stories.size() != storyIds.size()) {
            throw new ResourceNotFoundException("One or more stories not found");
        }
        
        int added = 0;
        int skipped = 0;
        
        // Get starting position
        int position = startPosition != null ? startPosition : collectionStoryRepository.getNextPosition(collectionId);
        
        for (UUID storyId : storyIds) {
            // Check if story is already in collection
            if (collectionStoryRepository.existsByCollectionIdAndStoryId(collectionId, storyId)) {
                skipped++;
                continue;
            }
            
            // Add story to collection
            Story story = stories.stream()
                .filter(s -> s.getId().equals(storyId))
                .findFirst()
                .orElseThrow();
            
            CollectionStory collectionStory = new CollectionStory(collection, story, position);
            collectionStoryRepository.save(collectionStory);
            
            added++;
            position += 1000; // Gap-based positioning
        }
        
        searchServiceAdapter.updateCollection(findById(collectionId));

        long totalStories = collectionStoryRepository.countByCollectionId(collectionId);
        
        logger.info("Added {} stories to collection {}, skipped {} duplicates", added, collectionId, skipped);
        
        return Map.of(
            "added", added,
            "skipped", skipped,
            "totalStories", totalStories
        );
    }
    
    /**
     * Remove a story from a collection
     */
    public void removeStoryFromCollection(UUID collectionId, UUID storyId) {
        if (!collectionStoryRepository.existsByCollectionIdAndStoryId(collectionId, storyId)) {
            throw new ResourceNotFoundException("Story not found in collection");
        }
        
        CollectionStory collectionStory = collectionStoryRepository.findByCollectionIdAndStoryId(collectionId, storyId);
        collectionStoryRepository.delete(collectionStory);
        searchServiceAdapter.updateCollection(findById(collectionId));

        logger.info("Removed story {} from collection {}", storyId, collectionId);
    }
    
    /**
     * Reorder stories in a collection
     */
    @Transactional
    public void reorderStories(UUID collectionId, List<Map<String, Object>> storyOrders) {
        findByIdBasic(collectionId); // Validate collection exists
        
        // Two-phase update to avoid unique constraint violations:
        // Phase 1: Set all positions to negative values (temporary)
        logger.debug("Phase 1: Setting temporary negative positions for collection {}", collectionId);
        for (int i = 0; i < storyOrders.size(); i++) {
            Map<String, Object> order = storyOrders.get(i);
            UUID storyId = UUID.fromString(String.valueOf(order.get("storyId")));
            
            // Set temporary negative position to avoid conflicts
            collectionStoryRepository.updatePosition(collectionId, storyId, -(i + 1));
        }
        
        // Phase 2: Set final positions
        logger.debug("Phase 2: Setting final positions for collection {}", collectionId);
        for (Map<String, Object> order : storyOrders) {
            UUID storyId = UUID.fromString(String.valueOf(order.get("storyId")));
            Integer position = (Integer) order.get("position");
            
            collectionStoryRepository.updatePosition(collectionId, storyId, position * 1000); // Gap-based positioning
        }
        
        searchServiceAdapter.updateCollection(findById(collectionId));

        logger.info("Reordered {} stories in collection {}", storyOrders.size(), collectionId);
    }
    
    /**
     * Get story with collection reading context
     */
    public Map<String, Object> getStoryWithCollectionContext(UUID collectionId, UUID storyId) {
        Collection collection = findByIdBasic(collectionId);
        Story story = storyRepository.findById(storyId)
            .orElseThrow(() -> new ResourceNotFoundException("Story not found: " + storyId));
        
        // Find current position
        CollectionStory currentStory = collectionStoryRepository.findByCollectionIdAndStoryId(collectionId, storyId);
        if (currentStory == null) {
            throw new ResourceNotFoundException("Story not found in collection");
        }
        
        // Find previous and next stories
        List<CollectionStory> previousStories = collectionStoryRepository.findPreviousStory(collectionId, storyId);
        List<CollectionStory> nextStories = collectionStoryRepository.findNextStory(collectionId, storyId);
        
        UUID previousStoryId = previousStories.isEmpty() ? null : previousStories.get(0).getStory().getId();
        UUID nextStoryId = nextStories.isEmpty() ? null : nextStories.get(0).getStory().getId();
        
        // Get current position in collection
        List<CollectionStory> allStories = collectionStoryRepository.findByCollectionIdOrderByPosition(collectionId);
        int currentPosition = 0;
        for (int i = 0; i < allStories.size(); i++) {
            if (allStories.get(i).getStory().getId().equals(storyId)) {
                currentPosition = i + 1;
                break;
            }
        }
        
        Map<String, Object> collectionContext = Map.of(
            "id", collection.getId(),
            "name", collection.getName(),
            "currentPosition", currentPosition,
            "totalStories", allStories.size(),
            "previousStoryId", previousStoryId != null ? previousStoryId : "",
            "nextStoryId", nextStoryId != null ? nextStoryId : ""
        );
        
        return Map.of(
            "story", convertToReadingDto(story),
            "collection", collectionContext
        );
    }
    
    /**
     * Get collection statistics
     */
    public Map<String, Object> getCollectionStatistics(UUID collectionId) {
        Collection collection = findById(collectionId);
        
        List<CollectionStory> collectionStories = collection.getCollectionStories();
        
        // Calculate statistics
        int totalStories = collectionStories.size();
        int totalWordCount = collectionStories.stream()
            .mapToInt(cs -> cs.getStory().getWordCount() != null ? cs.getStory().getWordCount() : 0)
            .sum();
        int estimatedReadingTime = readingTimeService.calculateReadingTime(totalWordCount);
        
        double averageStoryRating = collectionStories.stream()
            .filter(cs -> cs.getStory().getRating() != null)
            .mapToInt(cs -> cs.getStory().getRating())
            .average()
            .orElse(0.0);
        
        double averageWordCount = totalStories > 0 ? (double) totalWordCount / totalStories : 0.0;
        
        // Tag frequency
        Map<String, Long> tagFrequency = collectionStories.stream()
            .flatMap(cs -> cs.getStory().getTags().stream())
            .collect(Collectors.groupingBy(Tag::getName, Collectors.counting()));
        
        // Author distribution
        List<Map<String, Object>> authorDistribution = collectionStories.stream()
            .filter(cs -> cs.getStory().getAuthor() != null)
            .collect(Collectors.groupingBy(cs -> cs.getStory().getAuthor().getName(), Collectors.counting()))
            .entrySet().stream()
            .map(entry -> Map.<String, Object>of(
                "authorName", entry.getKey(),
                "storyCount", entry.getValue()
            ))
            .sorted((a, b) -> Long.compare((Long) b.get("storyCount"), (Long) a.get("storyCount")))
            .collect(Collectors.toList());
        
        return Map.of(
            "totalStories", totalStories,
            "totalWordCount", totalWordCount,
            "estimatedReadingTime", estimatedReadingTime,
            "averageStoryRating", Math.round(averageStoryRating * 100.0) / 100.0,
            "averageWordCount", Math.round(averageWordCount),
            "tagFrequency", tagFrequency,
            "authorDistribution", authorDistribution
        );
    }
    
    /**
     * Find or create tags by names
     */
    private Set<Tag> findOrCreateTags(List<String> tagNames) {
        Set<Tag> tags = new HashSet<>();
        
        for (String tagName : tagNames) {
            String trimmedName = tagName.trim();
            if (!trimmedName.isEmpty()) {
                Tag tag = tagRepository.findByName(trimmedName)
                    .orElseGet(() -> {
                        Tag newTag = new Tag();
                        newTag.setName(trimmedName);
                        return tagRepository.save(newTag);
                    });
                tags.add(tag);
            }
        }
        
        return tags;
    }
    
    /**
     * Get collections that contain a specific story
     */
    public List<Collection> getCollectionsForStory(UUID storyId) {
        List<CollectionStory> collectionStories = collectionStoryRepository.findByStoryId(storyId);
        return collectionStories.stream()
                .map(CollectionStory::getCollection)
                .collect(Collectors.toList());
    }
    
    /**
     * Create a collection from all stories in a series, ordered by volume
     */
    public Collection createCollectionFromSeries(UUID seriesId) {
        com.storycove.entity.Series series = seriesRepository.findById(seriesId)
            .orElseThrow(() -> new ResourceNotFoundException("Series not found with id: " + seriesId));

        List<UUID> storyIds = series.getStories().stream()
            .sorted(java.util.Comparator.comparingDouble((Story s) -> s.getVolume() != null ? s.getVolume() : Double.MAX_VALUE))
            .map(Story::getId)
            .collect(Collectors.toList());

        if (storyIds.isEmpty()) {
            throw new IllegalStateException("Cannot create a collection from a series with no stories.");
        }

        logger.info("Creating collection from series '{}' with {} stories", series.getName(), storyIds.size());
        return createCollection(series.getName(), series.getDescription(), null, storyIds);
    }

    /**
     * Get all collections for indexing (used by SearchServiceAdapter)
     */
    public List<Collection> findAllForIndexing() {
        return collectionRepository.findAllActiveCollections();
    }
    
    private StoryReadingDto convertToReadingDto(Story story) {
        StoryReadingDto dto = new StoryReadingDto();
        dto.setId(story.getId());
        dto.setTitle(story.getTitle());
        dto.setSummary(story.getSummary());
        dto.setDescription(story.getDescription());
        dto.setContentHtml(story.getContentHtml());
        dto.setSourceUrl(story.getSourceUrl());
        dto.setCoverPath(story.getCoverPath());
        dto.setWordCount(story.getWordCount());
        dto.setRating(story.getRating());
        dto.setVolume(story.getVolume());
        dto.setCreatedAt(story.getCreatedAt());
        dto.setUpdatedAt(story.getUpdatedAt());
        
        // Reading progress fields
        dto.setIsRead(story.getIsRead());
        dto.setReadingPosition(story.getReadingPosition());
        dto.setLastReadAt(story.getLastReadAt());
        
        if (story.getAuthor() != null) {
            dto.setAuthorId(story.getAuthor().getId());
            dto.setAuthorName(story.getAuthor().getName());
        }
        
        if (story.getSeries() != null) {
            dto.setSeriesId(story.getSeries().getId());
            dto.setSeriesName(story.getSeries().getName());
        }
        
        dto.setTags(story.getTags().stream()
            .map(this::convertTagToDto)
            .collect(Collectors.toList()));
        
        return dto;
    }
    
    private TagDto convertTagToDto(Tag tag) {
        TagDto dto = new TagDto();
        dto.setId(tag.getId());
        dto.setName(tag.getName());
        dto.setStoryCount(tag.getStories().size());
        return dto;
    }
}