package com.storycove.repository;

import com.storycove.entity.Author;
import com.storycove.entity.Series;
import com.storycove.entity.Story;
import com.storycove.entity.Tag;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Repository
public interface StoryRepository extends JpaRepository<Story, UUID> {
    
    Optional<Story> findByTitle(String title);
    
    List<Story> findByTitleContainingIgnoreCase(String title);
    
    Page<Story> findByTitleContainingIgnoreCase(String title, Pageable pageable);
    
    List<Story> findByAuthor(Author author);
    
    Page<Story> findByAuthor(Author author, Pageable pageable);
    
    List<Story> findBySeries(Series series);
    
    Page<Story> findBySeries(Series series, Pageable pageable);
    
    @Query("SELECT s FROM Story s JOIN s.series ser WHERE ser.id = :seriesId ORDER BY s.volume ASC")
    List<Story> findBySeriesOrderByVolume(@Param("seriesId") UUID seriesId);
    
    @Query("SELECT s FROM Story s WHERE s.series.id = :seriesId AND s.volume = :volume")
    Optional<Story> findBySeriesAndVolume(@Param("seriesId") UUID seriesId, @Param("volume") Double volume);
    
    
    @Query("SELECT s FROM Story s JOIN s.tags t WHERE t = :tag")
    List<Story> findByTag(@Param("tag") Tag tag);
    
    @Query("SELECT s FROM Story s JOIN s.tags t WHERE t = :tag")
    Page<Story> findByTag(@Param("tag") Tag tag, Pageable pageable);
    
    @Query("SELECT DISTINCT s FROM Story s JOIN s.tags t WHERE t.name IN :tagNames")
    List<Story> findByTagNames(@Param("tagNames") List<String> tagNames);
    
    @Query("SELECT DISTINCT s FROM Story s JOIN s.tags t WHERE t.name IN :tagNames")
    Page<Story> findByTagNames(@Param("tagNames") List<String> tagNames, Pageable pageable);
    
    @Query("SELECT s FROM Story s WHERE s.rating >= :minRating ORDER BY s.rating DESC")
    List<Story> findByMinimumRating(@Param("minRating") Integer minRating);
    
    @Query("SELECT s FROM Story s WHERE s.rating >= :minRating ORDER BY s.rating DESC")
    Page<Story> findByMinimumRating(@Param("minRating") Integer minRating, Pageable pageable);
    
    @Query("SELECT s FROM Story s ORDER BY s.rating DESC")
    List<Story> findTopRatedStories();
    
    @Query("SELECT s FROM Story s ORDER BY s.rating DESC")
    Page<Story> findTopRatedStories(Pageable pageable);
    
    @Query("SELECT s FROM Story s WHERE s.wordCount BETWEEN :minWords AND :maxWords")
    List<Story> findByWordCountRange(@Param("minWords") Integer minWords, @Param("maxWords") Integer maxWords);
    
    @Query("SELECT s FROM Story s WHERE s.wordCount BETWEEN :minWords AND :maxWords")
    Page<Story> findByWordCountRange(@Param("minWords") Integer minWords, @Param("maxWords") Integer maxWords, Pageable pageable);
    
    
    
    @Query("SELECT s FROM Story s ORDER BY s.createdAt DESC")
    List<Story> findRecentlyAdded();
    
    @Query("SELECT s FROM Story s ORDER BY s.createdAt DESC")
    Page<Story> findRecentlyAdded(Pageable pageable);
    
    @Query("SELECT s FROM Story s WHERE s.sourceUrl IS NOT NULL")
    List<Story> findStoriesWithSourceUrl();
    
    @Query("SELECT s FROM Story s WHERE s.coverPath IS NOT NULL")
    List<Story> findStoriesWithCover();
    
    @Query("SELECT COUNT(s) FROM Story s WHERE s.createdAt >= :since")
    long countStoriesCreatedSince(@Param("since") LocalDateTime since);

    @Query("SELECT COUNT(s) FROM Story s WHERE s.createdAt >= :since OR s.updatedAt >= :since")
    long countStoriesModifiedAfter(@Param("since") LocalDateTime since);
    
    @Query("SELECT AVG(s.wordCount) FROM Story s")
    Double findAverageWordCount();
    
    @Query("SELECT AVG(s.rating) FROM Story s WHERE s.rating IS NOT NULL")
    Double findOverallAverageRating();
    
    @Query("SELECT SUM(s.wordCount) FROM Story s")
    Long findTotalWordCount();
    
    @Query("SELECT s FROM Story s WHERE s.title LIKE %:keyword% OR s.description LIKE %:keyword%")
    List<Story> findByKeyword(@Param("keyword") String keyword);
    
    @Query("SELECT s FROM Story s WHERE s.title LIKE %:keyword% OR s.description LIKE %:keyword%")
    Page<Story> findByKeyword(@Param("keyword") String keyword, Pageable pageable);
    
    boolean existsBySourceUrl(String sourceUrl);
    
    Optional<Story> findBySourceUrl(String sourceUrl);
    
    @Query("SELECT s FROM Story s WHERE s.createdAt >= :since ORDER BY s.createdAt DESC")
    List<Story> findRecentlyRead(@Param("since") LocalDateTime since);
    
    @Query("SELECT DISTINCT s FROM Story s " +
           "LEFT JOIN FETCH s.author " +
           "LEFT JOIN FETCH s.series " +
           "LEFT JOIN FETCH s.tags")
    List<Story> findAllWithAssociations();
    
    @Query("SELECT s FROM Story s WHERE UPPER(s.title) = UPPER(:title) AND UPPER(s.author.name) = UPPER(:authorName)")
    List<Story> findByTitleAndAuthorNameIgnoreCase(@Param("title") String title, @Param("authorName") String authorName);
    
    /**
     * Count all stories for random selection (no filters)
     */
    @Query(value = "SELECT COUNT(*) FROM stories", nativeQuery = true)
    long countAllStories();
    
    /**
     * Count stories matching tag name filter for random selection
     */
    @Query(value = "SELECT COUNT(DISTINCT s.id) FROM stories s " +
                   "JOIN story_tags st ON s.id = st.story_id " +
                   "JOIN tags t ON st.tag_id = t.id " +
                   "WHERE UPPER(t.name) = UPPER(?1)", 
           nativeQuery = true)
    long countStoriesByTagName(String tagName);
    
    /**
     * Find a random story using offset (no filters)
     */
    @Query(value = "SELECT s.* FROM stories s ORDER BY s.id OFFSET ?1 LIMIT 1", nativeQuery = true)
    Optional<Story> findRandomStory(long offset);
    
    /**
     * Find a random story matching tag name filter using offset
     */
    @Query(value = "SELECT s.* FROM stories s " +
                   "JOIN story_tags st ON s.id = st.story_id " +
                   "JOIN tags t ON st.tag_id = t.id " +
                   "WHERE UPPER(t.name) = UPPER(?1) " +
                   "ORDER BY s.id OFFSET ?2 LIMIT 1", 
           nativeQuery = true)
    Optional<Story> findRandomStoryByTagName(String tagName, long offset);
    
    /**
     * Count stories matching multiple tags (ALL tags must be present)
     */
    @Query(value = "SELECT COUNT(*) FROM (" +
                   "  SELECT DISTINCT s.id FROM stories s " +
                   "  JOIN story_tags st ON s.id = st.story_id " +
                   "  JOIN tags t ON st.tag_id = t.id " +
                   "  WHERE UPPER(t.name) IN (?1) " +
                   "  GROUP BY s.id " +
                   "  HAVING COUNT(DISTINCT t.name) = ?2" +
                   ") as matched_stories", 
           nativeQuery = true)
    long countStoriesByMultipleTags(List<String> upperCaseTagNames, int tagCount);
    
    /**
     * Find random story matching multiple tags (ALL tags must be present)
     */
    @Query(value = "SELECT s.* FROM stories s " +
                   "JOIN story_tags st ON s.id = st.story_id " +
                   "JOIN tags t ON st.tag_id = t.id " +
                   "WHERE UPPER(t.name) IN (?1) " +
                   "GROUP BY s.id, s.title, s.summary, s.description, s.content_html, s.content_plain, s.source_url, s.cover_path, s.word_count, s.rating, s.volume, s.is_read, s.reading_position, s.last_read_at, s.author_id, s.series_id, s.created_at, s.updated_at " +
                   "HAVING COUNT(DISTINCT t.name) = ?2 " +
                   "ORDER BY s.id OFFSET ?3 LIMIT 1", 
           nativeQuery = true)
    Optional<Story> findRandomStoryByMultipleTags(List<String> upperCaseTagNames, int tagCount, long offset);
    
    /**
     * Count stories matching text search (title, author, tags)
     */
    @Query(value = "SELECT COUNT(DISTINCT s.id) FROM stories s " +
                   "LEFT JOIN authors a ON s.author_id = a.id " +
                   "LEFT JOIN story_tags st ON s.id = st.story_id " +
                   "LEFT JOIN tags t ON st.tag_id = t.id " +
                   "WHERE (UPPER(s.title) LIKE UPPER(?1) OR UPPER(a.name) LIKE UPPER(?1) OR UPPER(t.name) LIKE UPPER(?1))", 
           nativeQuery = true)
    long countStoriesByTextSearch(String searchPattern);
    
    /**
     * Find random story matching text search (title, author, tags)
     */
    @Query(value = "SELECT DISTINCT s.* FROM stories s " +
                   "LEFT JOIN authors a ON s.author_id = a.id " +
                   "LEFT JOIN story_tags st ON s.id = st.story_id " +
                   "LEFT JOIN tags t ON st.tag_id = t.id " +
                   "WHERE (UPPER(s.title) LIKE UPPER(?1) OR UPPER(a.name) LIKE UPPER(?1) OR UPPER(t.name) LIKE UPPER(?1)) " +
                   "ORDER BY s.id OFFSET ?2 LIMIT 1", 
           nativeQuery = true)
    Optional<Story> findRandomStoryByTextSearch(String searchPattern, long offset);
    
    /**
     * Count stories matching both text search AND tags
     */
    @Query(value = "SELECT COUNT(DISTINCT s.id) FROM stories s " +
                   "LEFT JOIN authors a ON s.author_id = a.id " +
                   "LEFT JOIN story_tags st ON s.id = st.story_id " +
                   "LEFT JOIN tags t ON st.tag_id = t.id " +
                   "WHERE (UPPER(s.title) LIKE UPPER(?1) OR UPPER(a.name) LIKE UPPER(?1) OR UPPER(t.name) LIKE UPPER(?1)) " +
                   "AND s.id IN (" +
                   "  SELECT s2.id FROM stories s2 " +
                   "  JOIN story_tags st2 ON s2.id = st2.story_id " +
                   "  JOIN tags t2 ON st2.tag_id = t2.id " +
                   "  WHERE UPPER(t2.name) IN (?2) " +
                   "  GROUP BY s2.id " +
                   "  HAVING COUNT(DISTINCT t2.name) = ?3" +
                   ")", 
           nativeQuery = true)
    long countStoriesByTextSearchAndTags(String searchPattern, List<String> upperCaseTagNames, int tagCount);
    
    /**
     * Find random story matching both text search AND tags
     */
    @Query(value = "SELECT DISTINCT s.* FROM stories s " +
                   "LEFT JOIN authors a ON s.author_id = a.id " +
                   "LEFT JOIN story_tags st ON s.id = st.story_id " +
                   "LEFT JOIN tags t ON st.tag_id = t.id " +
                   "WHERE (UPPER(s.title) LIKE UPPER(?1) OR UPPER(a.name) LIKE UPPER(?1) OR UPPER(t.name) LIKE UPPER(?1)) " +
                   "AND s.id IN (" +
                   "  SELECT s2.id FROM stories s2 " +
                   "  JOIN story_tags st2 ON s2.id = st2.story_id " +
                   "  JOIN tags t2 ON st2.tag_id = t2.id " +
                   "  WHERE UPPER(t2.name) IN (?2) " +
                   "  GROUP BY s2.id " +
                   "  HAVING COUNT(DISTINCT t2.name) = ?3" +
                   ") " +
                   "ORDER BY s.id OFFSET ?4 LIMIT 1", 
           nativeQuery = true)
    Optional<Story> findRandomStoryByTextSearchAndTags(String searchPattern, List<String> upperCaseTagNames, int tagCount, long offset);
    
}