package org.raddatz.familienarchiv.person;

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


import org.raddatz.familienarchiv.person.PersonNameAliasDTO;
import org.raddatz.familienarchiv.person.PersonSummaryDTO;
import org.raddatz.familienarchiv.person.PersonUpdateDTO;
import org.raddatz.familienarchiv.document.Document;
import org.raddatz.familienarchiv.person.Person;
import org.raddatz.familienarchiv.person.PersonNameAlias;
import org.raddatz.familienarchiv.security.Permission;
import org.raddatz.familienarchiv.security.RequirePermission;
import org.raddatz.familienarchiv.document.DocumentService;
import org.raddatz.familienarchiv.person.PersonService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/persons")
@RequiredArgsConstructor
@Validated
public class PersonController {

    private final PersonService personService;
    private final DocumentService documentService;

    @GetMapping
    @RequirePermission(Permission.READ_ALL)
    public ResponseEntity<PersonSearchResult> getPersons(
            @RequestParam(required = false) String q,
            @RequestParam(required = false) PersonType type,
            @RequestParam(required = false) Boolean familyOnly,
            @RequestParam(required = false) Boolean hasDocuments,
            @RequestParam(required = false) Boolean provisional,
            // review=true reveals the import noise (transcriber view); absent/false keeps the
            // clean reader default (familyMember OR documentCount > 0). The explicit filters AND
            // within whichever base the review flag selects.
            @RequestParam(required = false, defaultValue = "false") boolean review,
            @RequestParam(required = false) String sort,
            @RequestParam(defaultValue = "0") @Min(0) int page,
            @RequestParam(defaultValue = "50") @Min(1) @Max(100) int size) {
        // Legacy top-N-by-document-count path (reader dashboard): preserved, wrapped in the
        // same envelope so /api/persons always returns one shape. It is explicitly NON-paged —
        // the top-N query returns the complete result, so PersonSearchResult.topN reports an
        // honest totalElements (= returned count) instead of pretending to be a page slice.
        if ("documentCount".equals(sort) && q == null) {
            int safeSize = Math.min(size, 50);
            List<PersonSummaryDTO> top = personService.findTopByDocumentCount(safeSize);
            return ResponseEntity.ok(PersonSearchResult.topN(top));
        }

        PersonFilter filter = PersonFilter.builder()
                .type(type)
                .familyOnly(familyOnly)
                .hasDocuments(hasDocuments)
                .provisional(provisional)
                .readerDefault(!review)
                .build();
        return ResponseEntity.ok(personService.search(filter, page, size, q));
    }

    @GetMapping("/{id}")
    @RequirePermission(Permission.READ_ALL)
    public Person getPerson(@PathVariable UUID id) {
        return personService.getById(id);
    }

    @GetMapping("/{id}/correspondents")
    public ResponseEntity<List<Person>> getCorrespondents(
            @PathVariable UUID id,
            @RequestParam(required = false) String q) {
        return ResponseEntity.ok(personService.findCorrespondents(id, q));
    }

    @GetMapping("/{id}/documents")
    public List<Document> getPersonDocuments(@PathVariable UUID id) {
        return documentService.getDocumentsBySender(id);
    }

    @GetMapping("/{id}/received-documents")
    public List<Document> getPersonReceivedDocuments(@PathVariable UUID id) {
        return documentService.getDocumentsByReceiver(id);
    }

    @PostMapping
    @RequirePermission(Permission.WRITE_ALL)
    public ResponseEntity<Person> createPerson(@Valid @RequestBody PersonUpdateDTO dto) {
        validatePersonNames(dto);
        if (dto.getFirstName() != null) dto.setFirstName(dto.getFirstName().trim());
        dto.setLastName(dto.getLastName().trim());
        if (dto.getTitle() != null) dto.setTitle(dto.getTitle().trim());
        return ResponseEntity.ok(personService.createPerson(dto));
    }

    @PutMapping("/{id}")
    @RequirePermission(Permission.WRITE_ALL)
    public ResponseEntity<Person> updatePerson(@PathVariable UUID id, @Valid @RequestBody PersonUpdateDTO dto) {
        validatePersonNames(dto);
        if (dto.getFirstName() != null) dto.setFirstName(dto.getFirstName().trim());
        dto.setLastName(dto.getLastName().trim());
        if (dto.getTitle() != null) dto.setTitle(dto.getTitle().trim());
        return ResponseEntity.ok(personService.updatePerson(id, dto));
    }

    private void validatePersonNames(PersonUpdateDTO dto) {
        if (dto.getLastName() == null || dto.getLastName().isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Nachname ist Pflichtfeld");
        }
        if (dto.getPersonType() == PersonType.PERSON
                && (dto.getFirstName() == null || dto.getFirstName().isBlank())) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Vorname ist Pflichtfeld");
        }
    }

    @PostMapping("/{id}/merge")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequirePermission(Permission.WRITE_ALL)
    public void mergePerson(@PathVariable UUID id, @RequestBody Map<String, String> body) {
        String targetIdStr = body.get("targetPersonId");
        if (targetIdStr == null || targetIdStr.isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "targetPersonId fehlt");
        }
        personService.mergePersons(id, UUID.fromString(targetIdStr));
    }

    // Dedicated state transition that clears the provisional flag. A separate verb (not a
    // mass-assignable DTO field) so provisional can never be smuggled in via create/update.
    @PatchMapping("/{id}/confirm")
    @RequirePermission(Permission.WRITE_ALL)
    public ResponseEntity<Person> confirmPerson(@PathVariable UUID id) {
        return ResponseEntity.ok(personService.confirmPerson(id));
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequirePermission(Permission.WRITE_ALL)
    public void deletePerson(@PathVariable UUID id) {
        personService.deletePerson(id);
    }

    // ─── Alias endpoints ────────────────────────────────────────────────────

    @GetMapping("/{id}/aliases")
    public List<PersonNameAlias> getAliases(@PathVariable UUID id) {
        return personService.getAliases(id);
    }

    @PostMapping("/{id}/aliases")
    @RequirePermission(Permission.WRITE_ALL)
    public PersonNameAlias addAlias(@PathVariable UUID id, @Valid @RequestBody PersonNameAliasDTO dto) {
        return personService.addAlias(id, dto);
    }

    @DeleteMapping("/{id}/aliases/{aliasId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    @RequirePermission(Permission.WRITE_ALL)
    public void removeAlias(@PathVariable UUID id, @PathVariable UUID aliasId) {
        personService.removeAlias(id, aliasId);
    }
}
