package com.developx.szpitale.ingest.source;

import com.developx.szpitale.model.RawHospitalRecord;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class MarkdownRegistrySource implements VoivodeshipSource {

    private static final Logger log = LoggerFactory.getLogger(MarkdownRegistrySource.class);

    private final Path researchDir;
    private final String voivodeshipSlug;

    public MarkdownRegistrySource(Path researchDir, String voivodeshipSlug) {
        this.researchDir = researchDir;
        this.voivodeshipSlug = voivodeshipSlug;
    }

    @Override
    public String voivodeship() {
        return voivodeshipSlug;
    }

    @Override
    public List<RawHospitalRecord> fetch() {
        Path researchFile = findResearchFile(voivodeshipSlug);
        if (researchFile == null) {
            log.warn("No research file found for voivodeship: {}", voivodeshipSlug);
            return List.of();
        }
        return parseMarkdownFile(researchFile);
    }

    private Path findResearchFile(String voivodeship) {
        Path path = researchDir.resolve("research-" + voivodeship + ".md");
        if (Files.exists(path)) {
            return path;
        }
        path = researchDir.resolve("research-" + voivodeship + ".markdown");
        if (Files.exists(path)) {
            return path;
        }
        return null;
    }

    private List<RawHospitalRecord> parseMarkdownFile(Path filePath) {
        List<RawHospitalRecord> records = new ArrayList<>();
        try {
            String content = Files.readString(filePath);
            parseContent(content, records);
        } catch (IOException e) {
            log.error("Failed to read research file: {}", filePath, e);
        }
        return records;
    }

    private void parseContent(String content, List<RawHospitalRecord> records) {
        String[] sections = content.split("## \\d+\\. ");
        for (int i = 1; i < sections.length; i++) {
            RawHospitalRecord record = parseHospitalSection(sections[i]);
            if (record != null) {
                records.add(record);
            }
        }
        // Also try to parse with just "## " prefix (fallback)
        if (records.isEmpty()) {
            String[] fallbackSections = content.split("## ");
            for (int i = 1; i < fallbackSections.length; i++) {
                if (!fallbackSections[i].contains("Nota metodologiczna")
                        && !fallbackSections[i].contains("Rozbiezności")
                        && !fallbackSections[i].contains("Główne luki")
                        && !fallbackSections[i].contains("Rekomendacje")) {
                    RawHospitalRecord record = parseHospitalSection(fallbackSections[i]);
                    if (record != null && record.hospitalName() != null && !record.hospitalName().contains("Nota")) {
                        records.add(record);
                    }
                }
            }
        }
    }

    private RawHospitalRecord parseHospitalSection(String section) {
        String[] lines = section.split("\n", 3);
        if (lines.length < 2) return null;

        String headerLine = lines[0].trim();
        String hospitalName = headerLine.replace("*", "").trim();

        if (hospitalName.isEmpty() || hospitalName.startsWith("|")) return null;

        String body = lines.length > 2 ? lines[2] : "";
        String legalForm = extractLegalForm(body);
        String foundingBody = extractFoundingBody(body);
        String supervisoryBodyType = extractSupervisoryBodyType(body);

        List<RawHospitalRecord.RawPersonEntry> persons = parsePersonTables(section, legalForm, supervisoryBodyType,
                extractWebsite(section), hospitalName);

        boolean hasData = persons.stream().anyMatch(p -> p.sourceUrl() != null && !p.sourceUrl().isEmpty() && !p.sourceUrl().contains("brak"));

        return new RawHospitalRecord(
                hospitalName,
                extractShortName(hospitalName),
                extractCity(hospitalName),
                voivodeship(),
                legalForm,
                foundingBody,
                supervisoryBodyType,
                null,
                extractKrs(body),
                extractWebsite(headerLine + "\n" + body),
                persons
        );
    }

    private List<RawHospitalRecord.RawPersonEntry> parsePersonTables(String section, String defaultLegalForm,
                                                     String defaultSupervisory, String defaultWebsite, String hospitalName) {
        List<RawHospitalRecord.RawPersonEntry> entries = new ArrayList<>();

        Document doc = Jsoup.parse(section);
        Elements tables = doc.select("table");

        for (Element table : tables) {
            Elements rows = table.select("tr");
            if (rows.size() < 2) continue;

            Elements headerCells = rows.get(0).select("th, td");
            boolean isPersonTable = headerCells.stream()
                    .map(Element::text)
                    .collect(java.util.stream.Collectors.joining(" "))
                    .contains("Funkcja") &&
                    headerCells.stream()
                    .map(Element::text)
                    .collect(java.util.stream.Collectors.joining(" "))
                    .contains("Imię");

            if (!isPersonTable) continue;

            for (int i = 1; i < rows.size(); i++) {
                Element row = rows.get(i);
                Elements cells = row.select("td");
                if (cells.isEmpty()) continue;

                if (cells.get(0).text().contains("brak danych") ||
                    cells.get(0).text().contains("brak imiennego")) {
                    break;
                }

                String function = cleanText(cells.get(0).html());
                String fullName = cleanText(cells.get(1).html());
                String affiliation = cells.size() > 2 ? cleanText(cells.get(2).html()) : "brak/niepotwierdzona";
                String sourceUrl = cells.size() > 3 ? extractUrl(cells.get(3).html()) : "";

                if (fullName.isEmpty() || "brak".equalsIgnoreCase(fullName)) continue;
                if (function.contains("brak") && fullName.contains("brak danych")) continue;

                String organ = detectOrgan(function, defaultSupervisory, defaultLegalForm);
                String confidence = mapConfidence(affiliation);
                String status = mapStatus(function);

                // Extract display title from fullName
                String displayTitle = extractDisplayTitle(fullName, function);

                entries.add(new RawHospitalRecord.RawPersonEntry(
                        function, fullName, displayTitle, affiliation,
                        confidence, sourceUrl, organ, status
                ));
            }
        }

        if (entries.isEmpty()) {
            // Fallback: try to parse single-line entries
            parseFallbackEntries(section, entries, defaultSupervisory, defaultLegalForm);
        }

        return entries;
    }

    private void parseFallbackEntries(String section, List<RawHospitalRecord.RawPersonEntry> entries,
                                       String defaultSupervisory, String defaultLegalForm) {
        String[] lines = section.split("\n");
        Pattern personPattern = Pattern.compile(
                "^\\s*\\|\\s*(Dyrektor|Z-ca|p\\.o\\.|Rada|Naczelna|Główne)\\b[^|]*\\|\\s*([^|]+)\\|([^|]*)\\|([^|]*)\\s*$",
                Pattern.MULTILINE
        );

        for (String line : lines) {
            Matcher matcher = personPattern.matcher(line);
            if (matcher.find()) {
                String function = cleanText(matcher.group(1));
                String fullName = cleanText(matcher.group(2)).trim();
                String affiliation = cleanText(matcher.group(3)).trim();
                String sourceUrl = cleanText(matcher.group(4)).trim();

                if (!fullName.isEmpty()) {
                    entries.add(new RawHospitalRecord.RawPersonEntry(
                            function, fullName, null, affiliation,
                            mapConfidence(affiliation), sourceUrl,
                            detectOrgan(function, defaultSupervisory, defaultLegalForm), ""
                    ));
                }
            }
        }
    }

    private String detectOrgan(String function, String defaultSupervisory, String defaultLegalForm) {
        String lower = function.toLowerCase();
        if (lower.contains("dyrektor") && !lower.contains("z-ca") && !lower.contains("p.o.")) {
            return "DYREKCJA";
        }
        if (lower.contains("z-ca") || lower.contains("p.o.") ||
            lower.contains("główn") || lower.contains("naczel") ||
            lower.contains("przel")) {
            return "DYREKCJA";
        }
        if (lower.contains("rada")) {
            if (defaultSupervisory != null && defaultSupervisory.contains("NADZORCZA")) {
                return "RADA_NADZORCZA";
            }
            return "RADA_SPOLECZNA";
        }
        if (defaultSupervisory != null && defaultSupervisory.contains("NADZORCZA")) {
            return "RADA_NADZORCZA";
        }
        return "RADA_SPOLECZNA";
    }

    private String extractLegalForm(String body) {
        if (body.contains("sp. z o.o.") || body.contains("spółka")) {
            return "SPOLKA_Z_OO";
        }
        if (body.contains("Psychiatryczny")) {
            return "SP_PSYCHIATRYCZNY_ZOZ";
        }
        return "SPZOZ";
    }

    private String extractFoundingBody(String body) {
        var mat = Pattern.compile("Organ tworzący:\\s*(.+?)\\.(?:\\s|$)", Pattern.MULTILINE | Pattern.DOTALL).matcher(body);
        if (mat.find()) {
            return cleanText(mat.group(1)).trim();
        }
        return null;
    }

    private String extractSupervisoryBodyType(String body) {
        if (body.contains("Rada Nadzorcza") || body.contains("RADA NADZORCZA")) {
            return "RADA_NADZORCZA";
        }
        return "RADA_SPOLECZNA";
    }

    private String extractKrs(String body) {
        var mat = Pattern.compile("(?:KRS\\s*|krs\\s*:\\s*)(\\d{8,10})").matcher(body);
        if (mat.find()) {
            return mat.group(1);
        }
        return null;
    }

    private String extractWebsite(String text) {
        var mat = Pattern.compile("(https?://[^\\s|]+)").matcher(text);
        if (mat.find()) {
            return mat.group(1);
        }
        return null;
    }

    private String extractShortName(String fullName) {
        if (fullName.contains("Kliniczny") && fullName.contains("w ")) {
            var mat = Pattern.compile("([A-Z]{2,4})\\b")
                    .matcher(fullName.split("Kliniczny")[0]);
            if (mat.find()) return "USK";
        }
        return null;
    }

    private String extractCity(String hospitalName) {
        var mat = Pattern.compile("w\\s+([^,.(]+)").matcher(hospitalName);
        if (mat.find()) {
            return cleanText(mat.group(1)).trim();
        }
        return null;
    }

    private String extractDisplayTitle(String fullName, String function) {
        if (function.contains("Dyrektor Naczelny")) {
            return "Dyrektor Naczelny";
        }
        if (function.contains("p.o.")) {
            return "p.o. " + function.replace("p.o. ", "");
        }
        return null;
    }

    private String mapConfidence(String affiliation) {
        if (affiliation == null) return "BRAK_DANYCH";
        String lower = affiliation.toLowerCase();
        if (lower.contains("brak danych")) return "BRAK_DANYCH";
        if (lower.contains("niezweryfik") || lower.contains("NIEZWERYFIKOWANE")) return "NIEZWERYFIKOWANA";
        if (lower.contains("brak/niepotwierdzona") || lower.contains("niepotwierdzona") ||
            lower.contains("niepotwierdzone") || lower.contains("niepotwierdzony")) return "NIEPOTWIERDZONA";
        if (lower.contains("NIEZWERYFIKOWANE (zbieżność") || lower.contains("nieweryfikow")) return "NIEZWERYFIKOWANA";
        if (affiliation.contains("KO") || affiliation.contains("PO") ||
            affiliation.contains("PiS") || affiliation.contains("PSL") ||
            affiliation.contains("Trzecia Droga") || affiliation.contains("Polska 2050") ||
            affiliation.contains("KWW ") || affiliation.contains("KOMITET") ||
            affiliation.contains("Prawica") || affiliation.contains("SLD") ||
            affiliation.contains("Lewica")) {
            return "POTWIERDZONA";
        }
        return "NIEPOTWIERDZONA";
    }

    private String mapStatus(String function) {
        String lower = function.toLowerCase();
        if (lower.contains("p.o.") || lower.contains("pełniący")) return "PELNIACY_OBOWIAZKI";
        if (lower.contains("elekt")) return "ELEKT";
        if (lower.contains("były") || lower.contains("dawniej")) return "BYLY";
        return "AKTUALNY";
    }

    private String cleanText(String html) {
        if (html == null) return "";
        Document doc = Jsoup.parse(html);
        String text = doc.text().trim();
        // Remove trailing pipe if any
        if (text.endsWith("|")) text = text.substring(0, text.length() - 1).trim();
        return text;
    }

    private String extractUrl(String text) {
        String cleaned = cleanText(text);
        var mat = Pattern.compile("(https?://[^\\s]+)").matcher(cleaned);
        if (mat.find()) return mat.group(1).replaceAll("[)]+$", "");
        return cleaned.isEmpty() ? "" : cleaned;
    }

    public static List<VoivodeshipSource> discoverAllSources(Path researchDir) {
        List<VoivodeshipSource> sources = new ArrayList<>();
        try (var paths = Files.list(researchDir)) {
            paths.filter(p -> p.toString().endsWith(".md"))
                    .filter(p -> p.getFileName().toString().startsWith("research-"))
                    .forEach(p -> {
                        String voiv = p.getFileName().toString().replace("research-", "").replace(".md", "");
                        sources.add(new MarkdownRegistrySource(researchDir, voiv));
                        log.info("Discovered research source for voivodeship: {}", voiv);
                    });
        } catch (IOException e) {
            log.warn("Could not scan research directory: {}", researchDir);
        }
        return sources;
    }
}
