package org.raddatz.familienarchiv.importing;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.person.relationship.RelationType;
import org.raddatz.familienarchiv.person.relationship.RelationshipService;
import org.raddatz.familienarchiv.person.relationship.dto.NetworkDTO;
import org.raddatz.familienarchiv.person.relationship.dto.PersonNodeDTO;
import org.raddatz.familienarchiv.person.relationship.dto.RelationshipDTO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.File;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * Runs the four canonical loaders in their real dependency order — encoded explicitly
 * here, not implied by call order — and owns the async runner plus the {@link ImportStatus}
 * state machine the admin UI consumes. The orchestrator smoke-checks that all four
 * artifacts are present before starting, failing fast rather than half-loading tags but no
 * documents. A malformed artifact (a loader throwing) sets {@code FAILED}; an individual
 * bad file is surfaced through the {@link ImportStatus.SkippedFile} mechanism instead.
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CanonicalImportOrchestrator {

    private static final String TAG_TREE_ARTIFACT = "canonical-tag-tree.xlsx";
    private static final String PERSONS_ARTIFACT = "canonical-persons.xlsx";
    private static final String PERSONS_TREE_ARTIFACT = "canonical-persons-tree.json";
    private static final String DOCUMENTS_ARTIFACT = "canonical-documents.xlsx";

    private final TagTreeImporter tagTreeImporter;
    private final PersonRegisterImporter personRegisterImporter;
    private final PersonTreeImporter personTreeImporter;
    private final DocumentImporter documentImporter;
    private final RelationshipService relationshipService;

    @Value("${app.import.dir:/import}")
    private String canonicalDir;

    private volatile ImportStatus currentStatus = new ImportStatus(
            ImportStatus.State.IDLE, "IMPORT_IDLE", "Kein Import gestartet.", 0, List.of(), null);

    public ImportStatus getStatus() {
        return currentStatus;
    }

    @Async
    public void runImportAsync() {
        if (currentStatus.state() == ImportStatus.State.RUNNING) {
            throw DomainException.conflict(ErrorCode.IMPORT_ALREADY_RUNNING, "A mass import is already in progress");
        }
        runImport();
    }

    /** Synchronous entry point — wrapped by {@link #runImportAsync()} and called directly in tests. */
    void runImport() {
        currentStatus = new ImportStatus(ImportStatus.State.RUNNING, "IMPORT_RUNNING",
                "Import läuft...", 0, List.of(), LocalDateTime.now());
        try {
            File tagTree = requireArtifact(TAG_TREE_ARTIFACT);
            File persons = requireArtifact(PERSONS_ARTIFACT);
            File personsTree = requireArtifact(PERSONS_TREE_ARTIFACT);
            File documents = requireArtifact(DOCUMENTS_ARTIFACT);

            // Dependency DAG: documents need persons + tags; the tree needs persons.
            tagTreeImporter.load(tagTree);
            personRegisterImporter.load(persons);
            personTreeImporter.load(personsTree);
            warnOnGenerationMonotonicityViolations();
            DocumentImporter.LoadResult result = documentImporter.load(documents);

            currentStatus = new ImportStatus(ImportStatus.State.DONE, "IMPORT_DONE",
                    "Import abgeschlossen. " + result.processed() + " Dokumente verarbeitet.",
                    result.processed(), result.skippedFiles(), currentStatus.startedAt());
        } catch (DomainException e) {
            log.error("Canonical import failed: {}", e.getMessage());
            currentStatus = new ImportStatus(ImportStatus.State.FAILED, "IMPORT_FAILED_ARTIFACT",
                    "Fehler: " + e.getMessage(), 0, List.of(), currentStatus.startedAt());
        } catch (Exception e) {
            log.error("Canonical import failed", e);
            currentStatus = new ImportStatus(ImportStatus.State.FAILED, "IMPORT_FAILED_INTERNAL",
                    "Fehler: " + e.getMessage(), 0, List.of(), currentStatus.startedAt());
        }
    }

    private File requireArtifact(String name) {
        File artifact = new File(canonicalDir, name);
        if (!artifact.isFile()) {
            throw DomainException.badRequest(ErrorCode.IMPORT_ARTIFACT_INVALID,
                    "Missing canonical artifact: " + name);
        }
        return artifact;
    }

    /**
     * Walks every PARENT_OF edge in the family graph and logs a WARN whenever a child's
     * generation is not strictly deeper than its parent's. Soft check only — the import
     * is never aborted; the warning is a forensic signal for the curator. Reads through
     * {@link RelationshipService} so the orchestrator stays within the layering rule
     * (no direct repository access).
     */
    private void warnOnGenerationMonotonicityViolations() {
        NetworkDTO network = relationshipService.getFamilyNetwork();
        Map<UUID, PersonNodeDTO> byId = new HashMap<>(network.nodes().size());
        for (PersonNodeDTO node : network.nodes()) {
            byId.put(node.id(), node);
        }
        for (RelationshipDTO edge : network.edges()) {
            if (edge.relationType() != RelationType.PARENT_OF) continue;
            PersonNodeDTO parent = byId.get(edge.personId());
            PersonNodeDTO child = byId.get(edge.relatedPersonId());
            if (parent == null || child == null) continue;
            Integer pg = parent.generation();
            Integer cg = child.generation();
            if (pg != null && cg != null && cg <= pg) {
                log.warn("Generation monotonicity violation: parent {} (G{}) -> child {} (G{})",
                        parent.displayName(), pg, child.displayName(), cg);
            }
        }
    }
}
