package at.procon.ted.service.attachment;

import at.procon.ted.config.TedProcessorProperties;
import at.procon.ted.model.entity.ProcessedAttachment;
import at.procon.ted.model.entity.ProcessedAttachment.ProcessingStatus;
import at.procon.ted.repository.ProcessedAttachmentRepository;
import at.procon.ted.util.HashUtils;
import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode;
import at.procon.dip.runtime.config.RuntimeMode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileOutputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Main service for processing mail attachments.
 * Handles idempotency via content hash and delegates to format-specific extractors.
 *
 * @author Martin.Schweitzer@procon.co.at and claude.ai
 */
@Service
@ConditionalOnRuntimeMode(RuntimeMode.LEGACY)
@RequiredArgsConstructor
@Slf4j
public class AttachmentProcessingService {

    private final ProcessedAttachmentRepository attachmentRepository;
    private final List<AttachmentExtractor> extractors;
    private final TedProcessorProperties properties;

    /**
     * Process an attachment with idempotency check.
     * Returns the processed attachment entity or null if it's a duplicate.
     *
     * @param data         raw attachment data
     * @param filename     original filename
     * @param contentType  MIME content type
     * @param mailSubject  subject of the source email
     * @param mailFrom     sender of the source email
     * @param parentHash   hash of parent attachment (for ZIP-extracted files)
     * @return processing result with entity and child attachments
     */
    @Transactional
    public ProcessingResult processAttachment(byte[] data, String filename, String contentType,
            String mailSubject, String mailFrom, String parentHash) {

        // Calculate content hash for idempotency
        String contentHash = HashUtils.computeSha256(data);

        log.debug("Processing attachment: filename='{}', contentType='{}', hash={}",
                filename, contentType, contentHash);

        // Check if already processed
        Optional<ProcessedAttachment> existing = attachmentRepository.findByContentHash(contentHash);
        if (existing.isPresent()) {
            ProcessedAttachment existingAttachment = existing.get();
            log.info("Attachment already processed (duplicate): filename='{}', hash={}, status={}",
                    filename, contentHash, existingAttachment.getProcessingStatus());

            return ProcessingResult.duplicate(existingAttachment);
        }

        // Determine file type from extension
        String fileType = extractFileType(filename);

        // Create entity
        ProcessedAttachment attachment = ProcessedAttachment.builder()
                .contentHash(contentHash)
                .originalFilename(filename)
                .fileType(fileType)
                .contentType(contentType)
                .fileSize((long) data.length)
                .processingStatus(ProcessingStatus.PROCESSING)
                .mailSubject(mailSubject)
                .mailFrom(mailFrom)
                .parentHash(parentHash)
                .receivedAt(LocalDateTime.now())
                .build();

        // Save immediately to mark as being processed
        attachment = attachmentRepository.save(attachment);

        try {
            // Save attachment to disk
            String savedPath = saveAttachmentToDisk(data, filename, contentHash);
            attachment.setSavedPath(savedPath);

            // Find appropriate extractor
            AttachmentExtractor extractor = findExtractor(filename, contentType);
            List<AttachmentExtractor.ChildAttachment> childAttachments = new ArrayList<>();

            if (extractor != null) {
                log.debug("Using extractor {} for file '{}'",
                        extractor.getClass().getSimpleName(), filename);

                AttachmentExtractor.ExtractionResult result = extractor.extract(data, filename, contentType);

                if (result.success()) {
                    if (result.extractedText() != null && !result.extractedText().isBlank()) {
                        attachment.setExtractedText(result.extractedText());
                        log.debug("Extracted {} characters of text from '{}'",
                                result.extractedText().length(), filename);
                    }

                    if (result.childAttachments() != null && !result.childAttachments().isEmpty()) {
                        childAttachments.addAll(result.childAttachments());
                        attachment.setChildCount(childAttachments.size());
                        log.debug("Extracted {} child attachments from '{}'",
                                childAttachments.size(), filename);
                    }
                } else {
                    attachment.setErrorMessage(result.errorMessage());
                    log.warn("Extraction failed for '{}': {}", filename, result.errorMessage());
                }
            } else {
                log.debug("No extractor available for file type '{}' ({})", fileType, contentType);
            }

            // Mark as completed
            attachment.setProcessingStatus(ProcessingStatus.COMPLETED);
            attachment.setProcessedAt(LocalDateTime.now());
            attachment = attachmentRepository.save(attachment);

            log.info("Successfully processed attachment: filename='{}', hash={}, extractedText={}, children={}",
                    filename, contentHash,
                    attachment.getExtractedText() != null ? attachment.getExtractedText().length() + " chars" : "none",
                    childAttachments.size());

            return ProcessingResult.success(attachment, childAttachments);

        } catch (Exception e) {
            log.error("Failed to process attachment '{}': {}", filename, e.getMessage(), e);

            attachment.setProcessingStatus(ProcessingStatus.FAILED);
            attachment.setErrorMessage(e.getMessage());
            attachment.setProcessedAt(LocalDateTime.now());
            attachmentRepository.save(attachment);

            return ProcessingResult.failure(attachment, e.getMessage());
        }
    }

    /**
     * Find an extractor that can handle the given file.
     */
    private AttachmentExtractor findExtractor(String filename, String contentType) {
        for (AttachmentExtractor extractor : extractors) {
            if (extractor.canHandle(filename, contentType)) {
                return extractor;
            }
        }
        return null;
    }

    /**
     * Extract file type/extension from filename.
     */
    private String extractFileType(String filename) {
        if (filename == null) {
            return "unknown";
        }
        int lastDot = filename.lastIndexOf('.');
        if (lastDot > 0 && lastDot < filename.length() - 1) {
            return filename.substring(lastDot + 1).toLowerCase();
        }
        return "unknown";
    }

    /**
     * Save attachment to disk with hash-based naming.
     */
    private String saveAttachmentToDisk(byte[] data, String filename, String contentHash) throws Exception {
        String outputDir = properties.getMail().getAttachmentOutputDirectory();
        File dir = new File(outputDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        // Create filename with timestamp and hash for uniqueness
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        String safeFilename = sanitizeFilename(filename);
        String shortHash = contentHash.substring(0, 8);
        String outputFilename = timestamp + "_" + shortHash + "_" + safeFilename;

        File outputFile = new File(dir, outputFilename);

        try (FileOutputStream fos = new FileOutputStream(outputFile)) {
            fos.write(data);
        }

        log.debug("Saved attachment to: {}", outputFile.getAbsolutePath());
        return outputFile.getAbsolutePath();
    }

    /**
     * Sanitize filename for filesystem safety.
     */
    private String sanitizeFilename(String filename) {
        if (filename == null) {
            return "unnamed";
        }
        return filename.replaceAll("[\\\\/:*?\"<>|]", "_");
    }

    /**
     * Result of attachment processing.
     */
    public record ProcessingResult(
            ProcessedAttachment attachment,
            List<AttachmentExtractor.ChildAttachment> childAttachments,
            boolean isDuplicate,
            boolean isSuccess,
            String errorMessage
    ) {
        public static ProcessingResult success(ProcessedAttachment attachment,
                List<AttachmentExtractor.ChildAttachment> children) {
            return new ProcessingResult(attachment, children, false, true, null);
        }

        public static ProcessingResult duplicate(ProcessedAttachment attachment) {
            return new ProcessingResult(attachment, List.of(), true, true, null);
        }

        public static ProcessingResult failure(ProcessedAttachment attachment, String error) {
            return new ProcessingResult(attachment, List.of(), false, false, error);
        }

        public boolean hasChildren() {
            return childAttachments != null && !childAttachments.isEmpty();
        }
    }
}
