package com.example.ldp.api.controller;

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

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.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
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.BulkListingStatusRequest;
import com.example.ldp.api.dto.BulkListingStatusResultDto;
import com.example.ldp.api.dto.DanceAdminRowDto;
import com.example.ldp.api.dto.DeleteDancePreviewDto;
import com.example.ldp.api.dto.PageResponse;
import com.example.ldp.api.dto.merge.DanceMergeConfirmRequest;
import com.example.ldp.api.dto.merge.DanceMergePreviewRequest;
import com.example.ldp.api.dto.merge.DanceMergePreviewResponse;
import com.example.ldp.api.dto.merge.DanceMergeResultDto;
import com.example.ldp.api.dto.merge.DuplicateGroupDto;
import com.example.ldp.domain.Dance;
import com.example.ldp.legal.ContentRestrictionPath;
import com.example.ldp.security.AuthUtil;
import com.example.ldp.service.AdminDanceService;
import com.example.ldp.service.DanceMergeService;
import com.example.ldp.service.DanceService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

/**
 * Admin-only endpoints for dance management. Provides richer data than the
 * public catalog.
 */
@RestController
@RequestMapping("/api/admin/dances")
@RequiredArgsConstructor
@PreAuthorize("hasRole('ADMIN')")
public class AdminDanceController {

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

    private final AdminDanceService adminDanceService;
    private final DanceMergeService danceMergeService;
    private final DanceService danceService;
    private final AuthUtil authUtil;

    /**
     * List all dances for admin management with optional filters and
     * pagination. Returns enriched DTOs with primary song and choreographers.
     *
     * @param page Número de pàgina (0-indexed, default 0)
     * @param size Mida de pàgina (default 25, màxim 50)
     */
    @GetMapping
    public PageResponse<DanceAdminRowDto> listDances(
            @RequestParam(required = false) String q,
            @RequestParam(required = false) Long id,
            @RequestParam(required = false) String level,
            @RequestParam(required = false) Integer counts,
            @RequestParam(required = false) Integer walls,
            @RequestParam(required = false) String danceType,
            @RequestParam(required = false) Integer yearFrom,
            @RequestParam(required = false) Integer yearTo,
            @RequestParam(required = false) Boolean hasPrimarySong,
            @RequestParam(required = false) String listingStatus,
            @RequestParam(required = false) Boolean hasSpec,
            @RequestParam(required = false) Boolean hasPrimaryVideo,
            @RequestParam(required = false) Boolean unknownYear,
            @RequestParam(required = false) Boolean unknownCounts,
            @RequestParam(required = false) Boolean hasChoreographer,
            /** T-502: false = els balls sense propietari (els que hereta l'administracio). */
            @RequestParam(required = false) Boolean hasOwner,
            @RequestParam(required = false) String choreographerCountry,
            @RequestParam(required = false, defaultValue = "name") String sortBy,
            @RequestParam(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);

        return adminDanceService.listDances(q, id, level, counts, walls, danceType, yearFrom, yearTo,
                hasPrimarySong, listingStatus, hasSpec, hasPrimaryVideo, unknownYear, unknownCounts,
                hasChoreographer, hasOwner, choreographerCountry, sortBy, sortDir,
                safePage, safeSize);
    }

    /**
     * T-502 -- <b>APROVA</b> una proposta que espera revisió: REVIEW -> PUBLISHED.
     *
     * <p>És una porta diferent de {@code PATCH /{id}/listing-status}, que és el comandament
     * manual de sempre. Aquesta tanca la petició de revisió i en deixa el rastre; l'altra
     * mou l'estat i prou. Barrejar-les faria que aprovar des de la safata i publicar des del
     * llistat deixessin registres diferents del mateix acte.
     */
    @ContentRestrictionPath(value = ContentRestrictionPath.Kind.NOT_A_RESTRICTION,
            reason = "Aprovar PUBLICA el ball: el fa visible, no el restringeix. Cap de les mesures de l'art. 17.1 del DSA.")
    @PostMapping("/{id}/review/approve")
    public Map<String, String> approveReview(
            @PathVariable Long id,
            @RequestBody(required = false) Map<String, String> body
    ) {
        String note = body != null ? body.get("note") : null;
        danceService.approveReview(id, note);
        return Map.of("id", id.toString(), "listingStatus", "PUBLISHED");
    }

    /**
     * T-502 -- <b>TORNA</b> una proposta a l'autor amb un motiu: REVIEW -> DRAFT.
     *
     * <p>El motiu és <b>obligatori</b> ({@code ContentReviewService} rebutja el buit), i no
     * per burocràcia: qui ens ha aportat un contingut té dret a saber per què no es publica.
     * Vegeu el javadoc de {@code ContentReview} sobre l'art. 17 del DSA i sobre què queda
     * pendent que contesti l'advocat (T-339).
     */
    @ContentRestrictionPath(value = ContentRestrictionPath.Kind.MODERATION_MANUAL,
            reason = "Tornar una proposta ES una denegacio de publicacio sobre contingut aportat per un usuari: el motiu s'exigeix, es desa a content_review i s'envia a l'autor (el correu, a T-504). Es el cami que l'art. 17 del DSA governa i per aixo el motiu no es opcional.")
    @PostMapping("/{id}/review/return")
    public Map<String, String> returnReview(
            @PathVariable Long id,
            @RequestBody Map<String, String> body
    ) {
        danceService.returnReview(id, body != null ? body.get("reason") : null);
        return Map.of("id", id.toString(), "listingStatus", "DRAFT");
    }

    /**
     * Canvia l'estat de publicació d'un ball. Body: { "listingStatus":
     * "PUBLISHED" }
     */
    @ContentRestrictionPath(value = ContentRestrictionPath.Kind.MODERATION_MANUAL,
            reason = "ADMIN canvia l'estat de publicacio d'un ball. Cap a ARCHIVED/DRAFT es una restriccio de visibilitat (art. 17.1.a): decisio a ma des de la safata.")
    @PatchMapping("/{id}/listing-status")
    public Map<String, String> updateListingStatus(
            @PathVariable Long id,
            @RequestBody Map<String, String> body
    ) {
        String newStatus = body.get("listingStatus");
        adminDanceService.updateListingStatus(id, newStatus);
        return Map.of("id", id.toString(), "listingStatus", newStatus);
    }

    /**
     * T-409: canvi massiu d'estat de publicació. Cas d'ús real: filtrar per
     * DRAFT i publicar-los tots de cop, en una sola crida (no un bucle de PATCH
     * individuals des del client). Validació per fila: {@code failures} porta
     * els ids que no s'han pogut tocar (p. ex. un id inexistent) i el motiu.
     */
    @ContentRestrictionPath(value = ContentRestrictionPath.Kind.MODERATION_MANUAL,
            reason = "El mateix que updateListingStatus pero en bloc. Que siguin molts alhora NO en canvia la naturalesa: l'art. 17 es deu per cada contingut restringit, no per operacio.")
    @PatchMapping("/bulk-status")
    public BulkListingStatusResultDto bulkUpdateListingStatus(
            @Valid @RequestBody BulkListingStatusRequest request) {
        return adminDanceService.bulkUpdateListingStatus(request.ids(), request.listingStatus());
    }

    /**
     * Audit metadata for a dance (admin-only).
     * Public detail endpoint omits these fields.
     */
    @GetMapping("/{id}/audit")
    public AuditDto getAudit(@PathVariable Long id) {
        Dance d = danceService.get(id);
        return new AuditDto(
                d.getCreatedAt(),
                d.getUpdatedAt(),
                d.getCreatedBy(),
                d.getLastModifiedBy()
        );
    }

    /**
     * Pre-flight summary before hard-deleting a dance. Reports blockers
     * (events using it, merge survivor) and the cascade footprint so the
     * admin can decide whether to delete or archive.
     */
    @GetMapping("/{id}/delete-preview")
    public DeleteDancePreviewDto previewDelete(@PathVariable Long id) {
        return adminDanceService.previewDelete(id);
    }

    /**
     * Hard-delete a dance. Admin must pass the exact dance name as
     * {@code confirmationName} query param. Refuses if any blocker is
     * present (use the archive flow instead, via PATCH /listing-status).
     */
    @ContentRestrictionPath(value = ContentRestrictionPath.Kind.MODERATION_MANUAL,
            reason = "ADMIN esborra un ball sencer (amb previsualitzacio i confirmacio per nom, ADR-004). Cami de retirada de l'art. 17.1.a: decisio motivada a ma des de la safata.")
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteDance(
            @PathVariable Long id,
            @RequestParam String confirmationName) {
        adminDanceService.deleteDance(id, confirmationName, authUtil.getCurrentUser());
    }

    /**
     * Associa una cançó existent a un ball com a PRIMARY.
     * Body: { "songId": 123 }
     */
    @PostMapping("/{id}/songs")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void linkSong(
            @PathVariable Long id,
            @RequestBody Map<String, Long> body
    ) {
        Long songId = body.get("songId");
        if (songId == null) {
            throw new com.example.ldp.service.exception.BadRequestException("songId is required");
        }
        adminDanceService.linkSongToDance(id, songId);
    }

    // ── Dance Merge / Deduplication ──

    @GetMapping("/duplicates")
    public List<DuplicateGroupDto> detectDuplicates() {
        return danceMergeService.detectDuplicates();
    }

    @PostMapping("/merge/preview")
    public DanceMergePreviewResponse mergePreview(
            @Valid @RequestBody DanceMergePreviewRequest request) {
        return danceMergeService.preview(request);
    }

    @PostMapping("/merge/execute")
    public DanceMergeResultDto mergeExecute(
            @Valid @RequestBody DanceMergeConfirmRequest request) {
        return danceMergeService.executeMerge(request);
    }
}
