package com.example.ldp.api;

import java.util.List;
import java.util.Map;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.example.ldp.api.dto.AuditDto;
import com.example.ldp.api.dto.DeleteSongPreviewDto;
import com.example.ldp.api.dto.PageResponse;
import com.example.ldp.api.dto.SongListItemDto;
import com.example.ldp.api.mapper.SongMapper;
import com.example.ldp.api.util.SortBuilder;
import com.example.ldp.domain.Link;
import com.example.ldp.domain.LinkKind;
import com.example.ldp.domain.Song;
import com.example.ldp.security.AuthUtil;
import com.example.ldp.service.SongService;
import com.example.ldp.service.exception.NotFoundException;

/**
 * Controller d'administració per a cançons.
 * 
 * CARACTERÍSTIQUES:
 * - Només accessible per usuaris amb rol ADMIN
 * - Filtres avançats (any, BPM, gèneres, hasPreview, hasSpotify, hasApple)
 * - Límit de size = 50 (més generós per admin)
 * - DTO complet amb tota la informació
 */
@RestController
@RequestMapping("/api/admin/songs")
@PreAuthorize("hasRole('ADMIN')")
public class AdminSongController {

    private static final int MAX_PAGE_SIZE = 50;
    private static final int DEFAULT_PAGE_SIZE = 25;

    /** Whitelist de camps ordenables (nom públic → propietat JPA). */
    private static final Map<String, String> SORTABLE_FIELDS = Map.of(
            "id", "id",
            "title", "title",
            "artist", "artist",
            "year", "year",
            "bpm", "bpm",
            "durationSeconds", "durationSeconds"
    );
    private static final String DEFAULT_SORT = "title";

    private final SongService service;
    private final SongMapper mapper;
    private final AuthUtil authUtil;

    public AdminSongController(SongService service, SongMapper mapper, AuthUtil authUtil) {
        this.service = service;
        this.mapper = mapper;
        this.authUtil = authUtil;
    }

    /**
     * Cerca avançada de cançons per administradors.
     * 
     * @param q Cerca per títol o artista (opcional)
     * @param yearFrom Any mínim (opcional)
     * @param yearTo Any màxim (opcional)
     * @param bpmFrom BPM mínim (opcional)
     * @param bpmTo BPM màxim (opcional)
     * @param genres Gèneres a filtrar (OR) (opcional)
     * @param hasPreview Filtrar per si té preview (opcional)
     * @param hasSpotify Filtrar per si té link Spotify (opcional)
     * @param hasApple Filtrar per si té link Apple Music (opcional)
     * @param page Número de pàgina (default 0)
     * @param size Mida de pàgina (default 25, màxim 50)
     * @return Llista paginada de cançons amb DTO complet
     */
    @GetMapping
    public PageResponse<SongListItemDto> advancedSearch(
            @RequestParam(name = "q", required = false) String q,
            @RequestParam(name = "id", required = false) Long id,
            @RequestParam(name = "yearFrom", required = false) Integer yearFrom,
            @RequestParam(name = "yearTo", required = false) Integer yearTo,
            @RequestParam(name = "bpmFrom", required = false) Integer bpmFrom,
            @RequestParam(name = "bpmTo", required = false) Integer bpmTo,
            @RequestParam(name = "genres", required = false) List<String> genres,
            @RequestParam(name = "hasPreview", required = false) Boolean hasPreview,
            @RequestParam(name = "hasSpotify", required = false) Boolean hasSpotify,
            @RequestParam(name = "hasApple", required = false) Boolean hasApple,
            @RequestParam(name = "sortBy", required = false, defaultValue = DEFAULT_SORT) String sortBy,
            @RequestParam(name = "sortDir", required = false, defaultValue = "asc") String sortDir,
            @RequestParam(name = "page", defaultValue = "0") int page,
            @RequestParam(name = "size", defaultValue = "25") int size
    ) {
        // Aplicar límits de seguretat
        int safePage = Math.max(page, 0);
        int safeSize = Math.min(Math.max(size, 1), MAX_PAGE_SIZE);

        var pageable = PageRequest.of(safePage, safeSize,
                SortBuilder.build(sortBy, sortDir, SORTABLE_FIELDS, DEFAULT_SORT));
        
        // Cerca avançada amb tots els filtres
        Page<Song> results = service.advancedSearch(
                q, id, yearFrom, yearTo, bpmFrom, bpmTo, genres, hasPreview, pageable
        );
        
        // Obtenir links per filtrar hasSpotify/hasApple i per mapper
        List<Long> songIds = results.getContent().stream()
                .map(Song::getId)
                .toList();
        Map<Long, List<Link>> linksBySongId = service.linksForSongs(songIds);
        
        // Filtrar per hasSpotify i hasApple si s'especifiquen
        List<Song> filteredContent = results.getContent().stream()
                .filter(song -> matchesLinkFilters(song, linksBySongId, hasSpotify, hasApple))
                .toList();
        
        // Mapejar a DTO complet
        List<SongListItemDto> dtos = filteredContent.stream()
                .map(song -> mapper.toListItem(song, linksBySongId.getOrDefault(song.getId(), List.of())))
                .toList();
        
        // Crear resposta paginada manual (ja que hem filtrat en memòria)
        return new PageResponse<>(
                dtos,
                results.getNumber(),
                dtos.size(),
                results.getTotalElements(),
                results.getTotalPages()
        );
    }

    /**
     * Audit metadata for a song (admin-only).
     * Public detail endpoint omits these fields.
     */
    @GetMapping("/{id}/audit")
    public AuditDto getAudit(@PathVariable Long id) {
        Song s = service.get(id)
                .orElseThrow(() -> new NotFoundException("Cançó no trobada: " + id));
        return new AuditDto(
                s.getCreatedAt(),
                s.getUpdatedAt(),
                s.getCreatedBy(),
                s.getLastModifiedBy()
        );
    }

    /**
     * Pre-flight summary before hard-deleting a song. Reports blockers
     * (dance using it, merge survivor) and the cascade footprint. Mirror of
     * {@code AdminDanceController#previewDelete}.
     */
    @GetMapping("/{id}/delete-preview")
    public DeleteSongPreviewDto previewDelete(@PathVariable Long id) {
        return service.previewDelete(id);
    }

    /**
     * Hard-delete a song. Admin must pass the exact "Title — Artist" string as
     * {@code confirmationName} query param (note: em-dash, U+2014).
     */
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteSong(
            @PathVariable Long id,
            @RequestParam String confirmationName) {
        service.delete(id, confirmationName, authUtil.getCurrentUser());
    }

    /**
     * Comprova si una cançó compleix els filtres de links (hasSpotify, hasApple).
     */
    private boolean matchesLinkFilters(
            Song song, 
            Map<Long, List<Link>> linksBySongId,
            Boolean hasSpotify,
            Boolean hasApple
    ) {
        if (hasSpotify == null && hasApple == null) {
            return true; // Cap filtre de links
        }
        
        List<Link> links = linksBySongId.getOrDefault(song.getId(), List.of());
        boolean songHasSpotify = links.stream().anyMatch(l -> l.getKind() == LinkKind.SPOTIFY);
        boolean songHasApple = links.stream().anyMatch(l -> l.getKind() == LinkKind.APPLE);
        
        if (hasSpotify != null && hasSpotify != songHasSpotify) {
            return false;
        }
        if (hasApple != null && hasApple != songHasApple) {
            return false;
        }
        return true;
    }
}
