package click.kamil.springstatemachineexporter.html.exporter;

import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
import click.kamil.springstatemachineexporter.analysis.model.BusinessFlow;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
import click.kamil.springstatemachineexporter.analysis.model.FlowStep;
import click.kamil.springstatemachineexporter.analysis.model.LinkResolution;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.exporter.ExportOptions;
import click.kamil.springstatemachineexporter.exporter.PlantUml;
import click.kamil.springstatemachineexporter.exporter.StateMachineExporter;
import click.kamil.springstatemachineexporter.exporter.TransitionLinkKey;
import click.kamil.springstatemachineexporter.model.Transition;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.FileFormatOption;
import net.sourceforge.plantuml.SourceStringReader;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
public class HtmlExporter implements StateMachineExporter {

    private final PlantUml pumlExporter = new PlantUml();
    private final SvgDecorator svgDecorator = new SvgDecorator();
    private final ObjectMapper objectMapper = new ObjectMapper()
            .enable(SerializationFeature.INDENT_OUTPUT);

    @Override
    public String export(AnalysisResult result, ExportOptions options) {
        try {
            // 1. Generate PUML with identifiers embedded
            ExportOptions enrichedOptions = ExportOptions.builder()
                    .useLambdaGuards(options.isUseLambdaGuards())
                    .renderChoicesAsDiamonds(options.isRenderChoicesAsDiamonds())
                    .embedIdentifiers(true) // Force identifying classes in SVG
                    .eventFormat(options.getEventFormat())
                    .stateFormat(options.getStateFormat())
                    .build();

            String puml = pumlExporter.export(result, enrichedOptions);

            // 2. Generate SVG from PUML
            SourceStringReader reader = new SourceStringReader(puml);
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            reader.generateImage(os, new FileFormatOption(FileFormat.SVG));
            String rawSvg = os.toString(StandardCharsets.UTF_8);

            // 3. Decorate SVG (Clean responsive, styles, etc.)
            String decoratedSvg = svgDecorator.decorate(rawSvg);

            // 4. Load Template
            String template = loadTemplate();

            // 5. Prepare Metadata JSON (precompute link keys so HTML JS matches SVG identifiers)
            AnalysisResult htmlMetadata = enrichLinkKeys(result, options);
            String metadataJson = objectMapper.writeValueAsString(htmlMetadata);

            // Check if we should render metadata pane
            boolean hideMetadataProp = Boolean.getBoolean("html.hideMetadataPane");
            boolean hasEntryPoints = false;
            if (result.getMetadata() != null && result.getMetadata().getEntryPoints() != null && !result.getMetadata().getEntryPoints().isEmpty()) {
                Set<String> smEvents = result.getTransitions().stream()
                        .filter(t -> t.getEvent() != null)
                        .map(t -> {
                            String eventStr = t.getEvent().fullIdentifier() != null ? t.getEvent().fullIdentifier() : t.getEvent().rawName();
                            return eventStr == null ? null : eventStr.replaceAll("[^a-zA-Z0-9_]", "_");
                        })
                        .collect(Collectors.toSet());
                if (result.getMetadata().getCallChains() != null) {
                    hasEntryPoints = result.getMetadata().getCallChains().stream()
                            .anyMatch(HtmlExporter::isSidebarVisibleChain);
                }
            }
            boolean hasFlows = result.getFlows() != null && !result.getFlows().isEmpty();
            boolean shouldRenderPane = !hideMetadataProp && options.isIncludeMetadataPane() && (hasEntryPoints || hasFlows);

            // 6. Inject Data
            String finalHtml = template
                    .replace("{{MACHINE_NAME}}", result.getName())
                    .replace("{{SVG_CONTENT}}", decoratedSvg)
                    .replace("{{METADATA_JSON}}", metadataJson)
                    .replace("{{LINK_EVENT_FORMAT}}", options.getEventFormat().name())
                    .replace("{{LINK_STATE_FORMAT}}", options.getStateFormat().name());

            if (shouldRenderPane) {
                // If template.html uses {{SIDEBAR_HTML}} and {{TOGGLE_BUTTON_HTML}}, replace with actual HTML
                finalHtml = finalHtml.replace("{{SIDEBAR_HTML}}", "<div id=\"sidebar\">\n" +
                        "    <div class=\"sidebar-header\">\n" +
                        "        <header>\n" +
                        "            <h1>Explorer</h1>\n" +
                        "            <p>{{MACHINE_NAME}}</p>\n" +
                        "        </header>\n" +
                        "    </div>\n" +
                        "    \n" +
                        "    <div class=\"tabs\">\n" +
                        "        <div class=\"tab active\" data-tab=\"explorer\">Explorer</div>\n" +
                        "        <div class=\"tab\" data-tab=\"flows\">Business Flows</div>\n" +
                        "    </div>\n" +
                        "\n" +
                        "    <div id=\"tab-explorer\" class=\"tab-content active\">\n" +
                        "        <div id=\"ep-list\"></div>\n" +
                        "    </div>\n" +
                        "\n" +
                        "    <div id=\"tab-flows\" class=\"tab-content\">\n" +
                        "        <div id=\"flow-list\"></div>\n" +
                        "    </div>\n" +
                        "</div>").replace("{{MACHINE_NAME}}", result.getName());
                
                finalHtml = finalHtml.replace("{{TOGGLE_BUTTON_HTML}}", "    <button id=\"toggle-sidebar\" aria-label=\"Toggle Sidebar\" title=\"Toggle Sidebar\">\n" +
                        "        <svg viewBox=\"0 0 24 24\"><path d=\"M4 6h16M4 12h16M4 18h16\"></path></svg>\n" +
                        "    </button>");
            } else {
                finalHtml = finalHtml.replace("{{SIDEBAR_HTML}}", "").replace("{{TOGGLE_BUTTON_HTML}}", "");
            }

            return finalHtml;

        } catch (Exception e) {
            log.error("Failed to export to HTML", e);
            throw new RuntimeException("HTML export failed", e);
        }
    }

    private static boolean isSidebarVisibleChain(CallChain chain) {
        if (chain == null) {
            return false;
        }
        if (chain.getLinkResolution() == LinkResolution.UNRESOLVED_EXTERNAL) {
            return true;
        }
        TriggerPoint trigger = chain.getTriggerPoint();
        if (trigger != null && trigger.getEvent() != null && trigger.getEvent().startsWith("[LIFECYCLE:")) {
            return true;
        }
        if (chain.getMatchedTransitions() != null && !chain.getMatchedTransitions().isEmpty()) {
            LinkResolution resolution = chain.getLinkResolution();
            return resolution == null
                    || resolution == LinkResolution.RESOLVED
                    || resolution == LinkResolution.AMBIGUOUS_WIDEN;
        }
        return false;
    }

    private static AnalysisResult enrichLinkKeys(AnalysisResult result, ExportOptions options) {
        AnalysisResult.AnalysisResultBuilder builder = result.toBuilder();
        boolean changed = false;

        CodebaseMetadata metadata = result.getMetadata();
        if (metadata != null && metadata.getCallChains() != null && !metadata.getCallChains().isEmpty()) {
            List<CallChain> enrichedChains = metadata.getCallChains().stream()
                    .map(chain -> enrichChainLinkKeys(chain, options))
                    .toList();
            builder.metadata(metadata.toBuilder().callChains(enrichedChains).build());
            changed = true;
        }

        if (result.getFlows() != null && !result.getFlows().isEmpty()) {
            builder.flows(result.getFlows().stream()
                    .map(flow -> enrichFlowLinkKeys(flow, options))
                    .toList());
            changed = true;
        }

        return changed ? builder.build() : result;
    }

    private static CallChain enrichChainLinkKeys(CallChain chain, ExportOptions options) {
        if (chain.getMatchedTransitions() == null || chain.getMatchedTransitions().isEmpty()) {
            return chain;
        }
        List<MatchedTransition> enriched = chain.getMatchedTransitions().stream()
                .map(mt -> enrichMatchedTransitionLinkKey(mt, options))
                .toList();
        return chain.toBuilder().matchedTransitions(enriched).build();
    }

    private static MatchedTransition enrichMatchedTransitionLinkKey(MatchedTransition mt, ExportOptions options) {
        if (mt.getLinkKey() != null && !mt.getLinkKey().isBlank()) {
            return mt;
        }
        String linkKey = TransitionLinkKey.build(
                mt.getSourceState(),
                mt.getEvent(),
                options.getStateFormat(),
                options.getEventFormat());
        return mt.toBuilder().linkKey(linkKey).build();
    }

    private static BusinessFlow enrichFlowLinkKeys(BusinessFlow flow, ExportOptions options) {
        if (flow.getSteps() == null || flow.getSteps().isEmpty()) {
            return flow;
        }
        List<FlowStep> enrichedSteps = flow.getSteps().stream()
                .map(step -> enrichFlowStepLinkKey(step, options))
                .toList();
        return flow.toBuilder().steps(enrichedSteps).build();
    }

    private static FlowStep enrichFlowStepLinkKey(FlowStep step, ExportOptions options) {
        if (step.getLinkKey() != null && !step.getLinkKey().isBlank()) {
            return step;
        }
        if (step.getSource() == null || step.getSource().isBlank()
                || step.getEvent() == null || step.getEvent().isBlank()
                || step.getEvent().contains("->")) {
            return step;
        }
        String linkKey = TransitionLinkKey.build(
                step.getSource(),
                step.getEvent(),
                options.getStateFormat(),
                options.getEventFormat());
        return step.toBuilder().linkKey(linkKey).build();
    }

    private String loadTemplate() {
        try (InputStream is = getClass().getResourceAsStream("/html/template.html")) {
            if (is == null) throw new RuntimeException("HTML template not found");
            return new BufferedReader(new InputStreamReader(is))
                    .lines()
                    .collect(Collectors.joining("\n"));
        } catch (Exception e) {
            throw new RuntimeException("Failed to load HTML template", e);
        }
    }

    @Override
    public String export(String name, List<Transition> transitions, Set<String> startStates, Set<String> endStates, ExportOptions options) {
        AnalysisResult mockResult = AnalysisResult.builder()
                .name(name)
                .states(click.kamil.springstatemachineexporter.ast.app.TransitionStateUtils.findAllStates(transitions, startStates, endStates))
                .transitions(transitions)
                .startStates(startStates)
                .endStates(endStates)
                .build();
        return export(mockResult, options);
    }

    @Override
    public String getFileExtension() {
        return ".html";
    }
}
