package org.raddatz.familienarchiv.document;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;

import org.raddatz.familienarchiv.ocr.ScriptType;
import org.raddatz.familienarchiv.ocr.TrainingLabel;
import org.raddatz.familienarchiv.person.Person;
import org.raddatz.familienarchiv.tag.Tag;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

@NamedEntityGraph(name = "Document.full", attributeNodes = {
        @NamedAttributeNode("sender"),
        @NamedAttributeNode("receivers"),
        @NamedAttributeNode("tags"),
        @NamedAttributeNode("trainingLabels")
})
@NamedEntityGraph(name = "Document.list", attributeNodes = {
        @NamedAttributeNode("sender"),
        @NamedAttributeNode("receivers"),
        @NamedAttributeNode("tags")
})
@Entity
@Table(name = "documents")
@Data // Lombok: Generiert Getter, Setter, ToString, etc.
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Document {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private UUID id;

    @Column(nullable = false)
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private String title;

    // Der Dateiname im S3 (oder Pfad)
    @Column(name = "file_path")
    private String filePath;

    // MIME-Type, gespeichert beim Upload (z.B. "application/pdf", "image/jpeg")
    @Column(name = "content_type")
    private String contentType;

    // SHA-256 hash of the uploaded file — used to link annotations to a file version
    @Column(name = "file_hash", length = 64)
    private String fileHash;

    // S3 key of the generated thumbnail (e.g. "thumbnails/{docId}.jpg"); null until generated
    @Column(name = "thumbnail_key")
    private String thumbnailKey;

    @Column(name = "thumbnail_generated_at")
    private LocalDateTime thumbnailGeneratedAt;

    @Enumerated(EnumType.STRING)
    @Column(name = "thumbnail_aspect", length = 16)
    private ThumbnailAspect thumbnailAspect;

    @Column(name = "page_count")
    private Integer pageCount;

    // Originaler Dateiname beim Upload (z.B. "Brief_Oma_1940.pdf")
    @Column(name = "original_filename", nullable = false)
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private String originalFilename;

    // Der Status hilft uns beim Excel-Import Workflow
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private DocumentStatus status;

    // Metadaten aus dem Brief
    @Column(name = "meta_date")
    private LocalDate documentDate; // Wann wurde der Brief geschrieben?

    // Precision of documentDate — drives honest rendering ("ca. 1943", "Frühjahr 1943").
    // Verbatim mirror of the normalizer's Precision enum (see ADR-025).
    @Enumerated(EnumType.STRING)
    @Column(name = "meta_date_precision", nullable = false, length = 16)
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    @Builder.Default
    private DatePrecision metaDatePrecision = DatePrecision.UNKNOWN;

    // Range end — only set when metaDatePrecision is RANGE (open-ended ranges allowed → may be null).
    @Column(name = "meta_date_end")
    private LocalDate metaDateEnd;

    // Original date cell, verbatim, preserved for provenance and "as written" display.
    @Column(name = "meta_date_raw", columnDefinition = "TEXT")
    private String metaDateRaw;

    // Raw attribution preserved even when a person is linked via sender/receivers.
    @Column(name = "sender_text", columnDefinition = "TEXT")
    private String senderText;

    @Column(name = "receiver_text", columnDefinition = "TEXT")
    private String receiverText;

    @Column(name = "meta_location")
    private String location;

    @Column(name = "meta_document_location")
    private String documentLocation;

    // Physischer Archivstandort (aus ODS-Import)
    @Column(name = "archive_box")
    private String archiveBox;

    @Column(name = "archive_folder")
    private String archiveFolder;

    @Column(columnDefinition = "TEXT")
    private String transcription;

    @Column(columnDefinition = "TEXT")
    private String summary;

    @CreationTimestamp
    @Column(updatable = false)
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createdAt;

    @UpdateTimestamp
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime updatedAt;

    @Column(name = "metadata_complete", nullable = false)
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    @Builder.Default
    private boolean metadataComplete = false;

    @Enumerated(EnumType.STRING)
    @Column(name = "script_type", nullable = false)
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    @Builder.Default
    private ScriptType scriptType = ScriptType.UNKNOWN;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "document_receivers", joinColumns = @JoinColumn(name = "document_id"), inverseJoinColumns = @JoinColumn(name = "person_id"))
    @BatchSize(size = 50)
    @Builder.Default
    private Set<Person> receivers = new HashSet<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "sender_id")
    private Person sender;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "document_tags", joinColumns = @JoinColumn(name = "document_id"), inverseJoinColumns = @JoinColumn(name = "tag_id"))
    @BatchSize(size = 50)
    @Builder.Default
    private Set<Tag> tags = new HashSet<>();

    @ElementCollection(fetch = FetchType.LAZY)
    @CollectionTable(name = "document_training_labels", joinColumns = @JoinColumn(name = "document_id"))
    @Column(name = "label")
    @Enumerated(EnumType.STRING)
    @BatchSize(size = 50)
    @Builder.Default
    private Set<TrainingLabel> trainingLabels = new HashSet<>();

    // Not persisted — computed per detail fetch so read-only users can tell at first
    // paint whether there is a transcription to read (DocumentService.getDocumentById).
    @Transient
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    @Builder.Default
    private boolean hasTranscription = false;

    // The `?v={thumbnailGeneratedAt}` cache-buster is load-bearing: the thumbnail
    // endpoint sends `Cache-Control: private, max-age=31536000, immutable`
    // (DocumentController.getDocumentThumbnail). `immutable` is only safe because
    // this URL changes whenever the underlying file does. Dropping the query param
    // would let browsers serve a stale thumbnail for a year after the file is
    // replaced, and shared caches could leak one user's thumbnail to another
    // (CWE-525).
    @JsonProperty("thumbnailUrl")
    public String getThumbnailUrl() {
        if (thumbnailKey == null) return null;
        String base = "/api/documents/" + id + "/thumbnail";
        if (thumbnailGeneratedAt == null) return base;
        return base + "?v=" + URLEncoder.encode(thumbnailGeneratedAt.toString(), StandardCharsets.UTF_8);
    }
}
