package com.developx.szpitale.ingest;

import com.developx.szpitale.model.*;
import com.developx.szpitale.model.enums.*;
import com.developx.szpitale.ingest.normalize.NameNormalizer;
import com.developx.szpitale.ingest.normalize.PartyNormalizer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class CanonicalWriter {

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final NameNormalizer nameNormalizer = new NameNormalizer();
    private final PartyNormalizer partyNormalizer = new PartyNormalizer();

    public CanonicalWriter() {
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }

    public void write(Path outputDir, List<RawHospitalRecord> rawRecords, String voivodeship,
                       IngestResults results) throws IOException {
        Path canonicalDir = outputDir.resolve("canonical").normalize();
        Files.createDirectories(canonicalDir);

        List<Hospital> hospitals = new ArrayList<>();
        List<Person> people = new ArrayList<>();
        List<Role> roles = new ArrayList<>();
        List<Affiliation> affiliations = new ArrayList<>();

        for (RawHospitalRecord rawRecord : rawRecords) {
            String hospitalId = nameNormalizer.makeHospitalId(voivodeship, rawRecord.hospitalName());

            hospitals.add(new Hospital(
                    hospitalId,
                    rawRecord.hospitalName(),
                    rawRecord.shortName(),
                    rawRecord.city(),
                    voivodeship,
                    parseLegalForm(rawRecord.legalForm()),
                    rawRecord.foundingBody(),
                    parseSupervisoryBodyType(rawRecord.supervisoryBodyType()),
                    rawRecord.nip(),
                    rawRecord.krs(),
                    rawRecord.website(),
                    extractUniqueUrls(rawRecord.persons())
            ));

            Map<String, Person> peopleMap = new LinkedHashMap<>();
            Map<String, Set<String>> personUrlsMap = new LinkedHashMap<>();

            for (RawHospitalRecord.RawPersonEntry personEntry : rawRecord.persons()) {
                String fullName = personEntry.fullName();
                if (fullName.isEmpty()) continue;
                if (fullName.toLowerCase().contains("brak") &&
                    (personEntry.sourceUrl() == null || personEntry.sourceUrl().toLowerCase().contains("brak"))) {
                    continue;
                }

                String personId = nameNormalizer.makePersonId(fullName);
                List<String> titles = nameNormalizer.extractTitles(fullName);
                String extractedName = nameNormalizer.extractFullName(fullName);

                if (!peopleMap.containsKey(personId)) {
                    peopleMap.put(personId, new Person(
                            personId, extractedName, personEntry.displayTitle(),
                            titles, new ArrayList<>()
                    ));
                    personUrlsMap.put(personId, new LinkedHashSet<>());
                }

                if (personEntry.sourceUrl() != null && !personEntry.sourceUrl().isEmpty()) {
                    personUrlsMap.get(personId).add(personEntry.sourceUrl());
                }

                RoleType roleType = parseRoleType(personEntry.function());
                OrganType organ = parseOrganType(personEntry.organ());
                RoleStatus status = parseStatus(personEntry.statusNote());

                roles.add(new Role(
                        hospitalId, personId, roleType,
                        personEntry.function(),
                        organ, status, null, null,
                        null,
                        List.of(personEntry.sourceUrl())
                ));

                String affText = personEntry.partyAffiliation();
                if (affText != null && !affText.isEmpty() &&
                    !affText.toLowerCase().contains("brak") &&
                    !affText.toLowerCase().contains("bezpartyj")) {

                    String canonicalParty = partyNormalizer.canonicalize(affText);
                    AffiliationType affType = partyNormalizer.determineType(affText);
                    ConfidenceLevel confidence = partyNormalizer.mapConfidence(personEntry.confidence());

                    String note = affText.contains("historycznie")
                            ? "historycznie: " + affText
                            : affText;

                    affiliations.add(new Affiliation(
                            personId, affType, canonicalParty, confidence,
                            note,
                            List.of(personEntry.sourceUrl())
                    ));
                }
            }

            // Update people with merged URLs
            List<Person> updatedPeople = new ArrayList<>();
            for (Map.Entry<String, Set<String>> entry : personUrlsMap.entrySet()) {
                Person p = peopleMap.get(entry.getKey());
                if (p != null) {
                    updatedPeople.add(new Person(
                            p.id(), p.fullName(), p.displayName(),
                            p.titles(), new ArrayList<>(entry.getValue())
                    ));
                }
            }
            people.addAll(updatedPeople);
        }

        CanonicalDataset dataset = new CanonicalDataset(
                "1.0", voivodeship, LocalDateTime.now(),
                hospitals, people, affiliations, roles, List.of()
        );

        Path outputFile = canonicalDir.resolve(voivodeship + ".json");
        objectMapper.writerWithDefaultPrettyPrinter().writeValue(outputFile.toFile(), dataset);

        results.addHospitals(hospitals.size());
        results.addPeople(people.size());
        results.addRoles(roles.size());
        results.addAffiliations(affiliations.size());
    }

    private List<String> extractUniqueUrls(List<RawHospitalRecord.RawPersonEntry> persons) {
        return persons.stream()
                .map(RawHospitalRecord.RawPersonEntry::sourceUrl)
                .filter(url -> url != null && !url.isEmpty() && !url.toLowerCase().contains("brak"))
                .distinct()
                .collect(Collectors.toList());
    }

    private LegalForm parseLegalForm(String form) {
        if (form == null) return LegalForm.SPZOZ;
        String upper = form.toUpperCase();
        if (upper.contains("SPOLKA")) return LegalForm.SPOLKA_Z_OO;
        if (upper.contains("PSYCHIATRYCZNY")) return LegalForm.SP_PSYCHIATRYCZNY_ZOZ;
        return LegalForm.SPZOZ;
    }

    private SupervisoryBodyType parseSupervisoryBodyType(String type) {
        if (type == null) return SupervisoryBodyType.RADA_SPOLECZNA;
        return "NADZORCZA".equalsIgnoreCase(type)
                ? SupervisoryBodyType.RADA_NADZORCZA
                : SupervisoryBodyType.RADA_SPOLECZNA;
    }

    private RoleType parseRoleType(String function) {
        String lower = function.toLowerCase();
        if (lower.contains("dyrektor nacz")) {
            return RoleType.DYREKTOR;
        }
        if (lower.contains("dyrektor") && !lower.contains("z-ca") && !lower.contains("zast")) {
            return RoleType.DYREKTOR;
        }
        if (lower.contains("z-ca") || lower.contains("zast")) {
            return RoleType.ZASTEPCA_DYREKTORA;
        }
        if (lower.contains("glown") || lower.contains("gl. ")) {
            return RoleType.GLOWNY_KSIEGOWY;
        }
        if (lower.contains("naczel") && lower.contains("pieleg")) {
            return RoleType.PRZELOZONA_PIELEGNIARKOW;
        }
        if (lower.contains("przewodnicz") && !lower.contains("z-ca przewodnich")) {
            return RoleType.PRZEWODNICZACY_ORGANU;
        }
        if (lower.contains("sekte")) {
            return RoleType.SEKRETARZ_ORGANU;
        }
        if (lower.contains("z-ca")) {
            return RoleType.Z_CA_PRZEWODNICZACEGO;
        }
        return RoleType.CZLONEK_ORGANU_NADZORCZEGO;
    }

    private OrganType parseOrganType(String organ) {
        if (organ == null) return OrganType.RADA_SPOLECZNA;
        String upper = organ.toUpperCase();
        if (upper.contains("DYREKCJA")) return OrganType.DYREKCJA;
        if (upper.contains("NADZORCZA")) return OrganType.RADA_NADZORCZA;
        return OrganType.RADA_SPOLECZNA;
    }

    private RoleStatus parseStatus(String note) {
        if (note == null) return RoleStatus.AKTUALNY;
        String lower = note.toLowerCase();
        if (lower.contains("pelniac") || lower.contains("p.o.")) return RoleStatus.PELNIACY_OBOWIAZKI;
        if (lower.contains("elekt")) return RoleStatus.ELEKT;
        return RoleStatus.AKTUALNY;
    }
}
