package org.raddatz.familienarchiv.document.annotation;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.raddatz.familienarchiv.audit.AuditKind;
import org.raddatz.familienarchiv.audit.AuditService;
import org.raddatz.familienarchiv.document.annotation.CreateAnnotationDTO;
import org.raddatz.familienarchiv.document.annotation.UpdateAnnotationDTO;
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlockRepository;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.document.annotation.DocumentAnnotation;
import org.raddatz.familienarchiv.document.annotation.AnnotationRepository;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Slf4j
@Service
@RequiredArgsConstructor
public class AnnotationService {

    private final AnnotationRepository annotationRepository;
    private final TranscriptionBlockRepository transcriptionBlockRepository;
    private final AuditService auditService;

    public List<DocumentAnnotation> listAnnotations(UUID documentId) {
        return annotationRepository.findByDocumentId(documentId);
    }

    public Optional<DocumentAnnotation> findById(UUID id) {
        return annotationRepository.findById(id);
    }

    @Transactional
    public void deleteById(UUID annotationId) {
        annotationRepository.deleteById(annotationId);
    }

    @Transactional
    public void deleteAllById(java.util.Collection<UUID> annotationIds) {
        annotationRepository.deleteAllById(annotationIds);
    }

    @Transactional
    public DocumentAnnotation createAnnotation(UUID documentId, CreateAnnotationDTO dto, UUID userId, String fileHash) {
        DocumentAnnotation annotation = DocumentAnnotation.builder()
                .documentId(documentId)
                .pageNumber(dto.getPageNumber())
                .x(dto.getX())
                .y(dto.getY())
                .width(dto.getWidth())
                .height(dto.getHeight())
                .color(dto.getColor())
                .fileHash(fileHash)
                .createdBy(userId)
                .build();

        DocumentAnnotation saved = annotationRepository.save(annotation);
        auditService.logAfterCommit(AuditKind.ANNOTATION_CREATED, userId, saved.getDocumentId(),
                Map.of("pageNumber", saved.getPageNumber()));
        return saved;
    }

    @Transactional
    public DocumentAnnotation createOcrAnnotation(UUID documentId, CreateAnnotationDTO dto,
                                                   UUID userId, String fileHash,
                                                   List<List<Double>> polygon) {
        DocumentAnnotation annotation = DocumentAnnotation.builder()
                .documentId(documentId)
                .pageNumber(dto.getPageNumber())
                .x(dto.getX())
                .y(dto.getY())
                .width(dto.getWidth())
                .height(dto.getHeight())
                .color(dto.getColor())
                .fileHash(fileHash)
                .createdBy(userId)
                .polygon(polygon)
                .build();

        return annotationRepository.save(annotation);
    }

    @Transactional
    public DocumentAnnotation updateAnnotation(UUID documentId, UUID annotationId, UpdateAnnotationDTO dto) {
        DocumentAnnotation annotation = annotationRepository
                .findByIdAndDocumentId(annotationId, documentId)
                .orElseThrow(() -> DomainException.notFound(
                        ErrorCode.ANNOTATION_NOT_FOUND, "Annotation not found: " + annotationId));

        if (dto.getX() != null) annotation.setX(dto.getX());
        if (dto.getY() != null) annotation.setY(dto.getY());
        if (dto.getWidth() != null) annotation.setWidth(dto.getWidth());
        if (dto.getHeight() != null) annotation.setHeight(dto.getHeight());

        try {
            return annotationRepository.save(annotation);
        } catch (DataIntegrityViolationException e) {
            log.warn("Annotation bounds constraint violated for {}: {}", annotationId, e.getMessage());
            throw DomainException.badRequest(ErrorCode.ANNOTATION_UPDATE_FAILED, "Bounds out of range");
        }
    }

    @Transactional
    public void deleteAnnotation(UUID documentId, UUID annotationId, UUID userId) {
        DocumentAnnotation annotation = annotationRepository
                .findByIdAndDocumentId(annotationId, documentId)
                .orElseThrow(() -> DomainException.notFound(
                        ErrorCode.ANNOTATION_NOT_FOUND, "Annotation not found: " + annotationId));

        if (userId == null || !userId.equals(annotation.getCreatedBy())) {
            throw DomainException.forbidden("Only the annotation author can delete it");
        }

        transcriptionBlockRepository.deleteByAnnotationId(annotationId);
        annotationRepository.delete(annotation);
    }

    @Transactional
    public void backfillAnnotationFileHashForDocument(UUID documentId, String fileHash) {
        annotationRepository.findByDocumentIdAndFileHashIsNull(documentId).forEach(a -> {
            a.setFileHash(fileHash);
            annotationRepository.save(a);
        });
    }

}
