package org.raddatz.familienarchiv.document.transcription;

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.transcription.CreateTranscriptionBlockDTO;
import org.raddatz.familienarchiv.document.transcription.ReorderTranscriptionBlocksDTO;
import org.raddatz.familienarchiv.document.transcription.UpdateTranscriptionBlockDTO;
import org.raddatz.familienarchiv.document.annotation.AnnotationService;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.document.BlockSource;
import org.raddatz.familienarchiv.document.DocumentService;
import org.raddatz.familienarchiv.document.Document;
import org.raddatz.familienarchiv.document.annotation.DocumentAnnotation;
import org.raddatz.familienarchiv.ocr.ScriptType;
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlock;
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlockVersion;
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlockRepository;
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlockVersionRepository;
import org.raddatz.familienarchiv.ocr.SenderModelService;
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;

@Service
@RequiredArgsConstructor
@Slf4j
public class TranscriptionService {

    private static final String TRANSCRIPTION_COLOR = "#00C7B1";
    private static final int MAX_TEXT_LENGTH = 10_000;

    private final TranscriptionBlockRepository blockRepository;
    private final TranscriptionBlockVersionRepository versionRepository;
    private final AnnotationService annotationService;
    private final DocumentService documentService;
    private final SenderModelService senderModelService;
    private final AuditService auditService;

    public List<TranscriptionBlock> listBlocks(UUID documentId) {
        return blockRepository.findByDocumentIdOrderBySortOrderAsc(documentId);
    }

    @Transactional
    public void deleteByAnnotationId(UUID annotationId) {
        blockRepository.deleteByAnnotationId(annotationId);
    }

    public TranscriptionBlock getBlock(UUID documentId, UUID blockId) {
        return blockRepository.findByIdAndDocumentId(blockId, documentId)
                .orElseThrow(() -> DomainException.notFound(
                        ErrorCode.TRANSCRIPTION_BLOCK_NOT_FOUND,
                        "Transcription block not found: " + blockId));
    }

    @Transactional
    public TranscriptionBlock createBlock(UUID documentId, CreateTranscriptionBlockDTO dto, UUID userId) {
        Document doc = documentService.getDocumentById(documentId);

        CreateAnnotationDTO annotationDTO = new CreateAnnotationDTO(
                dto.getPageNumber(), dto.getX(), dto.getY(),
                dto.getWidth(), dto.getHeight(), TRANSCRIPTION_COLOR);
        DocumentAnnotation annotation = annotationService.createAnnotation(
                documentId, annotationDTO, userId, doc.getFileHash());

        int nextOrder = blockRepository.countByDocumentId(documentId);
        String text = sanitizeText(dto.getText());

        TranscriptionBlock block = TranscriptionBlock.builder()
                .annotationId(annotation.getId())
                .documentId(documentId)
                .text(text)
                .label(dto.getLabel())
                .sortOrder(nextOrder)
                .createdBy(userId)
                .updatedBy(userId)
                .build();

        TranscriptionBlock saved = blockRepository.save(block);
        saveVersion(saved, userId);
        log.info("Created transcription block {} for document {}", saved.getId(), documentId);
        return saved;
    }

    @Transactional
    public TranscriptionBlock createOcrBlock(UUID documentId, UUID annotationId,
                                              String text, int sortOrder, UUID userId) {
        String sanitized = sanitizeText(text);
        TranscriptionBlock block = TranscriptionBlock.builder()
                .annotationId(annotationId)
                .documentId(documentId)
                .text(sanitized)
                .sortOrder(sortOrder)
                .source(BlockSource.OCR)
                .createdBy(userId)
                .updatedBy(userId)
                .build();
        TranscriptionBlock saved = blockRepository.save(block);
        saveVersion(saved, userId);
        return saved;
    }

    /**
     * Upsert an OCR transcription block for a pre-existing annotation (guided OCR mode).
     * If the annotation already has a MANUAL block, it is left unchanged.
     * If it has an OCR block, the text is updated in-place.
     * If it has no block yet, a new OCR block is created.
     */
    @Transactional
    public TranscriptionBlock upsertGuidedBlock(UUID documentId, UUID annotationId,
                                                 String text, UUID userId) {
        return blockRepository.findByAnnotationId(annotationId).map(existing -> {
            if (existing.getSource() == BlockSource.MANUAL && !existing.getText().isBlank()) {
                return existing; // never overwrite non-empty manual transcription
            }
            existing.setText(sanitizeText(text));
            existing.setUpdatedBy(userId);
            TranscriptionBlock saved = blockRepository.save(existing);
            saveVersion(saved, userId);
            return saved;
        }).orElseGet(() -> createOcrBlock(documentId, annotationId, text, 0, userId));
    }

    @Transactional
    public TranscriptionBlock updateBlock(UUID documentId, UUID blockId,
                                          UpdateTranscriptionBlockDTO dto, UUID userId) {
        TranscriptionBlock block = getBlock(documentId, blockId);

        String previousText = block.getText();
        String text = sanitizeText(dto.getText());
        block.setText(text);
        block.setSource(BlockSource.MANUAL);
        if (dto.getLabel() != null) {
            block.setLabel(dto.getLabel());
        }
        block.getMentionedPersons().clear();
        block.getMentionedPersons().addAll(dto.getMentionedPersons());
        block.setUpdatedBy(userId);

        TranscriptionBlock saved = blockRepository.save(block);
        saveVersion(saved, userId);

        if (!text.equals(previousText)) {
            Optional<DocumentAnnotation> annotation = annotationService.findById(block.getAnnotationId());
            int pageNumber = annotation.map(DocumentAnnotation::getPageNumber).orElse(0);
            auditService.logAfterCommit(AuditKind.TEXT_SAVED, userId, documentId,
                    Map.of("pageNumber", pageNumber, "blockId", saved.getId().toString()));
        }

        Document doc = documentService.getDocumentById(documentId);
        if (doc.getSender() != null && doc.getScriptType() == ScriptType.HANDWRITING_KURRENT) {
            senderModelService.checkAndTriggerTraining(doc.getSender().getId());
        }

        return saved;
    }

    @Transactional
    public void deleteBlock(UUID documentId, UUID blockId) {
        TranscriptionBlock block = getBlock(documentId, blockId);
        UUID annotationId = block.getAnnotationId();

        // Block is the aggregate root — delete block first (cascades to versions + comments),
        // then delete the dependent annotation directly (no ownership check needed)
        blockRepository.delete(block);
        blockRepository.flush();
        annotationService.deleteById(annotationId);
        log.info("Deleted transcription block {} and annotation {} for document {}",
                blockId, annotationId, documentId);
    }

    @Transactional
    public void deleteAllBlocksByDocument(UUID documentId) {
        List<TranscriptionBlock> blocks = blockRepository.findByDocumentIdOrderBySortOrderAsc(documentId);
        if (blocks.isEmpty()) return;

        List<UUID> annotationIds = blocks.stream()
                .map(TranscriptionBlock::getAnnotationId)
                .toList();

        blockRepository.deleteAll(blocks);
        blockRepository.flush();
        annotationService.deleteAllById(annotationIds);
        log.info("Bulk-deleted {} transcription blocks for document {}", blocks.size(), documentId);
    }

    @Transactional
    public void reorderBlocks(UUID documentId, ReorderTranscriptionBlocksDTO dto) {
        List<UUID> blockIds = dto.getBlockIds();
        for (int i = 0; i < blockIds.size(); i++) {
            TranscriptionBlock block = getBlock(documentId, blockIds.get(i));
            block.setSortOrder(i);
            blockRepository.save(block);
        }
    }

    @Transactional
    public TranscriptionBlock reviewBlock(UUID documentId, UUID blockId, UUID userId) {
        TranscriptionBlock block = getBlock(documentId, blockId);
        boolean wasReviewed = block.isReviewed();
        block.setReviewed(!wasReviewed);
        TranscriptionBlock saved = blockRepository.save(block);
        if (!wasReviewed && saved.isReviewed()) {
            auditService.logAfterCommit(AuditKind.BLOCK_REVIEWED, userId, documentId, null);
        }
        return saved;
    }

    @Transactional
    public List<TranscriptionBlock> markAllBlocksReviewed(UUID documentId, UUID userId) {
        List<TranscriptionBlock> blocks = blockRepository.findByDocumentIdOrderBySortOrderAsc(documentId);
        for (TranscriptionBlock block : blocks) {
            if (!block.isReviewed()) {
                block.setReviewed(true);
                auditService.logAfterCommit(AuditKind.BLOCK_REVIEWED, userId, documentId, null);
            }
        }
        return blockRepository.saveAll(blocks);
    }

    public List<TranscriptionBlockVersion> getBlockHistory(UUID documentId, UUID blockId) {
        getBlock(documentId, blockId);
        return versionRepository.findByBlockIdOrderByChangedAtDesc(blockId);
    }

    private void saveVersion(TranscriptionBlock block, UUID userId) {
        TranscriptionBlockVersion version = TranscriptionBlockVersion.builder()
                .blockId(block.getId())
                .text(block.getText())
                .changedBy(userId)
                .build();
        versionRepository.save(version);
    }

    String sanitizeText(String text) {
        if (text == null) return "";
        if (text.length() > MAX_TEXT_LENGTH) {
            text = text.substring(0, MAX_TEXT_LENGTH);
        }
        return text;
    }

}
