package com.storycove.service;

import com.storycove.entity.Author;
import com.storycove.entity.Story;
import com.storycove.repository.AuthorRepository;
import com.storycove.service.exception.DuplicateResourceException;
import com.storycove.service.exception.ResourceNotFoundException;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Service
@Validated
@Transactional
public class AuthorService {

    private static final Logger logger = LoggerFactory.getLogger(AuthorService.class);

    private final AuthorRepository authorRepository;
    private final SearchServiceAdapter searchServiceAdapter;

    @Autowired
    public AuthorService(AuthorRepository authorRepository, SearchServiceAdapter searchServiceAdapter) {
        this.authorRepository = authorRepository;
        this.searchServiceAdapter = searchServiceAdapter;
    }

    @Transactional(readOnly = true)
    public List<Author> findAll() {
        return authorRepository.findAll();
    }
    
    @Transactional(readOnly = true)
    public List<Author> findAllWithStories() {
        List<Author> authors = authorRepository.findAll();
        // Force load lazy collections within transaction to avoid lazy loading issues
        authors.forEach(author -> {
            if (author.getStories() != null) {
                author.getStories().size(); // Force initialization
            }
            if (author.getUrls() != null) {
                author.getUrls().size(); // Force initialization
            }
        });
        return authors;
    }

    @Transactional(readOnly = true)
    public Page<Author> findAll(Pageable pageable) {
        return authorRepository.findAll(pageable);
    }

    @Transactional(readOnly = true)
    public Author findById(UUID id) {
        return authorRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Author", id.toString()));
    }

    @Transactional(readOnly = true)
    public Optional<Author> findByIdOptional(UUID id) {
        return authorRepository.findById(id);
    }

    @Transactional(readOnly = true)
    public Author findByName(String name) {
        return authorRepository.findByName(name)
                .orElseThrow(() -> new ResourceNotFoundException("Author", name));
    }

    @Transactional(readOnly = true)
    public Optional<Author> findByNameOptional(String name) {
        return authorRepository.findByName(name);
    }

    @Transactional(readOnly = true)
    public List<Author> searchByName(String name) {
        return authorRepository.findByNameContainingIgnoreCase(name);
    }

    @Transactional(readOnly = true)
    public Page<Author> searchByName(String name, Pageable pageable) {
        return authorRepository.findByNameContainingIgnoreCase(name, pageable);
    }

    @Transactional(readOnly = true)
    public List<Author> findAuthorsWithStories() {
        return authorRepository.findAuthorsWithStories();
    }

    @Transactional(readOnly = true)
    public Page<Author> findAuthorsWithStories(Pageable pageable) {
        return authorRepository.findAuthorsWithStories(pageable);
    }

    @Transactional(readOnly = true)
    public List<Author> findTopRatedAuthors() {
        return authorRepository.findTopRatedAuthors();
    }

    @Transactional(readOnly = true)
    public List<Author> findMostProlificAuthors() {
        return authorRepository.findMostProlificAuthors();
    }

    @Transactional(readOnly = true)
    public Page<Author> findMostProlificAuthors(Pageable pageable) {
        return authorRepository.findMostProlificAuthors(pageable);
    }

    @Transactional(readOnly = true)
    public List<Author> findByUrlDomain(String domain) {
        return authorRepository.findByUrlDomain(domain);
    }

    @Transactional(readOnly = true)
    public boolean existsByName(String name) {
        return authorRepository.existsByName(name);
    }

    public Author create(@Valid Author author) {
        validateAuthorForCreate(author);
        Author savedAuthor = authorRepository.save(author);
        
        // Index in Solr
        searchServiceAdapter.indexAuthor(savedAuthor);
        
        return savedAuthor;
    }

    public Author update(UUID id, @Valid Author authorUpdates) {
        Author existingAuthor = findById(id);
        
        // Check for name conflicts if name is being changed
        if (!existingAuthor.getName().equals(authorUpdates.getName()) && 
            existsByName(authorUpdates.getName())) {
            throw new DuplicateResourceException("Author", authorUpdates.getName());
        }

        updateAuthorFields(existingAuthor, authorUpdates);
        Author savedAuthor = authorRepository.save(existingAuthor);
        
        // Update in Solr
        searchServiceAdapter.updateAuthor(savedAuthor);
        
        return savedAuthor;
    }

    public void delete(UUID id) {
        Author author = findById(id);
        
        // Check if author has stories
        if (!author.getStories().isEmpty()) {
            throw new IllegalStateException("Cannot delete author with existing stories. Delete stories first or reassign them to another author.");
        }
        
        authorRepository.delete(author);
        
        // Remove from Solr
        searchServiceAdapter.deleteAuthor(id);
    }

    public Author addUrl(UUID id, String url) {
        Author author = findById(id);
        author.addUrl(url);
        Author savedAuthor = authorRepository.save(author);
        
        // Update in Solr
        searchServiceAdapter.updateAuthor(savedAuthor);
        
        return savedAuthor;
    }

    public Author removeUrl(UUID id, String url) {
        Author author = findById(id);
        author.removeUrl(url);
        Author savedAuthor = authorRepository.save(author);
        
        // Update in Solr
        searchServiceAdapter.updateAuthor(savedAuthor);
        
        return savedAuthor;
    }

    public Author setDirectRating(UUID id, int rating) {
        if (rating < 0 || rating > 5) {
            throw new IllegalArgumentException("Rating must be between 0 and 5");
        }
        
        Author author = findById(id);
        author.setAuthorRating(rating);
        return authorRepository.save(author);
    }
    
    public Author setRating(UUID id, Integer rating) {
        if (rating != null && (rating < 1 || rating > 5)) {
            throw new IllegalArgumentException("Rating must be between 1 and 5");
        }
        
        Author author = findById(id);
        logger.debug("Setting author rating: {} for author: {} (current rating: {})", 
                     rating, author.getName(), author.getAuthorRating());
        
        author.setAuthorRating(rating);
        authorRepository.save(author);
        
        // Flush and refresh to ensure the entity is up-to-date
        authorRepository.flush();
        Author refreshedAuthor = findById(id);
        
        logger.debug("Saved author rating: {} for author: {}", 
                     refreshedAuthor.getAuthorRating(), refreshedAuthor.getName());
        
        // Update in Solr
        searchServiceAdapter.updateAuthor(refreshedAuthor);
        
        return refreshedAuthor;
    }
    
    @Transactional(readOnly = true)
    public Integer getAuthorRatingFromDb(UUID id) {
        return authorRepository.findAuthorRatingById(id);
    }
    
    @Transactional(readOnly = true)
    public List<Author> findTopRated(Pageable pageable) {
        return authorRepository.findTopRatedAuthors(pageable).getContent();
    }
    
    @Transactional(readOnly = true)
    public Double calculateAverageStoryRating(UUID authorId) {
        Author author = findById(authorId);
        
        if (author.getStories() == null || author.getStories().isEmpty()) {
            return null;
        }
        
        List<Integer> ratings = author.getStories().stream()
                .map(Story::getRating)
                .filter(rating -> rating != null)
                .toList();
        
        if (ratings.isEmpty()) {
            return null;
        }
        
        return ratings.stream()
                .mapToInt(Integer::intValue)
                .average()
                .orElse(0.0);
    }

    public Author setAvatar(UUID id, String avatarPath) {
        Author author = findById(id);
        author.setAvatarImagePath(avatarPath);
        Author savedAuthor = authorRepository.save(author);
        
        // Update in Solr
        searchServiceAdapter.updateAuthor(savedAuthor);
        
        return savedAuthor;
    }

    public Author removeAvatar(UUID id) {
        Author author = findById(id);
        author.setAvatarImagePath(null);
        Author savedAuthor = authorRepository.save(author);
        
        // Update in Solr
        searchServiceAdapter.updateAuthor(savedAuthor);
        
        return savedAuthor;
    }

    @Transactional(readOnly = true)
    public long countRecentAuthors(int days) {
        java.time.LocalDateTime cutoffDate = java.time.LocalDateTime.now().minusDays(days);
        return authorRepository.countRecentAuthors(cutoffDate);
    }

    private void validateAuthorForCreate(Author author) {
        if (existsByName(author.getName())) {
            throw new DuplicateResourceException("Author", author.getName());
        }
    }

    private void updateAuthorFields(Author existing, Author updates) {
        if (updates.getName() != null) {
            existing.setName(updates.getName());
        }
        if (updates.getNotes() != null) {
            existing.setNotes(updates.getNotes());
        }
        if (updates.getAvatarImagePath() != null) {
            existing.setAvatarImagePath(updates.getAvatarImagePath());
        }
        if (updates.getAuthorRating() != null) {
            existing.setAuthorRating(updates.getAuthorRating());
        }
        if (updates.getUrls() != null) {
            // Create a defensive copy to avoid issues when existing and updates are the same object
            List<String> urlsCopy = new ArrayList<>(updates.getUrls());
            existing.getUrls().clear();
            existing.getUrls().addAll(urlsCopy);
        }
    }
}