package click.kamil.springstatemachineexporter.analysis.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.model.Transition;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.jackson.Jacksonized;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Getter
@Setter
@Builder(toBuilder = true)
@Jacksonized
@JsonIgnoreProperties(ignoreUnknown = true)
public class AnalysisResult {
    private String name;
    @Builder.Default
    private Set<click.kamil.springstatemachineexporter.model.State> states = java.util.Collections.emptySet();
    private List<Transition> transitions;
    private Set<String> startStates;
    private Set<String> endStates;
    private boolean renderChoicesAsDiamonds;

    @Builder.Default
    private List<BusinessFlow> flows = java.util.Collections.emptyList();

    /** Resolved {@code StateMachineConfigurerAdapter<S,E>} state type; persisted for JSON round-trip. */
    private String stateTypeFqn;
    /** Resolved {@code StateMachineConfigurerAdapter<S,E>} event type; persisted for JSON round-trip. */
    private String eventTypeFqn;

    @Builder.Default
    private CodebaseMetadata metadata = CodebaseMetadata.empty();

    public void applyResolution(Map<String, String> properties) {
        applyResolution(properties, null);
    }

    public void applyResolution(Map<String, String> properties, CodebaseContext context) {
        var resolver = new click.kamil.springstatemachineexporter.analysis.resolver.PropertyResolver();
        
        // 1. Resolve start/end states strings
        if (startStates != null) {
            this.startStates = startStates.stream()
                    .map(s -> resolver.resolveValue(s, properties))
                    .collect(java.util.stream.Collectors.toSet());
        }
        if (endStates != null) {
            this.endStates = endStates.stream()
                    .map(s -> resolver.resolveValue(s, properties))
                    .collect(java.util.stream.Collectors.toSet());
        }

        // 2. Resolve states (State record is immutable, so recreate)
        if (states != null) {
            this.states = states.stream()
                    .map(s -> click.kamil.springstatemachineexporter.model.State.of(
                            resolver.resolveValue(s.rawName(), properties),
                            resolver.resolveValue(s.fullIdentifier(), properties)))
                    .collect(java.util.stream.Collectors.toSet());
        }

        // 3. Resolve transitions
        if (transitions != null) {
            for (var t : transitions) {
                if (t.getEvent() != null) {
                    t.setEvent(click.kamil.springstatemachineexporter.model.Event.of(t.getEvent().rawName(), resolver.resolveValue(t.getEvent().fullIdentifier(), properties)));
                }
                // Resolve states within transitions
                t.setSourceStates(t.getSourceStates().stream()
                        .map(s -> click.kamil.springstatemachineexporter.model.State.of(s.rawName(), resolver.resolveValue(s.fullIdentifier(), properties)))
                        .collect(java.util.stream.Collectors.toList()));
                t.setTargetStates(t.getTargetStates().stream()
                        .map(s -> click.kamil.springstatemachineexporter.model.State.of(s.rawName(), resolver.resolveValue(s.fullIdentifier(), properties)))
                        .collect(java.util.stream.Collectors.toList()));
            }
        }

        // 4. Resolve metadata (triggers, entry points, call chains)
        if (metadata != null) {
            List<TriggerPoint> resolvedTriggers = metadata.getTriggers() == null ? null
                    : metadata.getTriggers().stream()
                            .map(trigger -> resolveTrigger(trigger, resolver, properties, context))
                            .collect(java.util.stream.Collectors.toList());

            List<CallChain> resolvedCallChains = metadata.getCallChains() == null ? null
                    : metadata.getCallChains().stream()
                            .map(chain -> resolveCallChain(chain, resolver, properties, context))
                            .collect(java.util.stream.Collectors.toList());

            if (metadata.getEntryPoints() != null) {
                for (var ep : metadata.getEntryPoints()) {
                    if (ep.getName() != null) {
                        ep.setName(resolver.resolveValue(ep.getName(), properties));
                    }
                    if (ep.getMetadata() != null) {
                        Map<String, String> resolvedMeta = new java.util.HashMap<>();
                        for (var entry : ep.getMetadata().entrySet()) {
                            resolvedMeta.put(entry.getKey(), resolver.resolveValue(entry.getValue(), properties));
                        }
                        ep.setMetadata(resolvedMeta);
                    }
                }
            }

            this.metadata = CodebaseMetadata.builder()
                    .triggers(resolvedTriggers != null ? resolvedTriggers : metadata.getTriggers())
                    .entryPoints(metadata.getEntryPoints())
                    .callChains(resolvedCallChains != null ? resolvedCallChains : metadata.getCallChains())
                    .properties(metadata.getProperties())
                    .build();
        }

        canonicalizeResolvedIdentifiers();
    }

    private void canonicalizeResolvedIdentifiers() {
        if (stateTypeFqn == null && eventTypeFqn == null) {
            return;
        }
        click.kamil.springstatemachineexporter.analysis.service.AnalysisResultFinalizer.applyCanonicalization(
                this,
                null,
                new StateMachineTypeResolver.MachineTypes(stateTypeFqn, eventTypeFqn));
    }

    private static TriggerPoint resolveTrigger(
            TriggerPoint trigger,
            click.kamil.springstatemachineexporter.analysis.resolver.PropertyResolver resolver,
            Map<String, String> properties,
            CodebaseContext context) {
        List<String> polymorphicEvents = trigger.getPolymorphicEvents() == null ? null
                : trigger.getPolymorphicEvents().stream()
                        .map(value -> resolver.resolveValue(value, properties))
                        .map(value -> MachineEnumCanonicalizer.qualifyEventIdentifier(
                                value, trigger.getEventTypeFqn(), context))
                        .collect(java.util.stream.Collectors.toList());
        String resolvedEvent = trigger.getEvent() != null ? resolver.resolveValue(trigger.getEvent(), properties) : null;
        String resolvedSource = trigger.getSourceState() != null
                ? resolver.resolveValue(trigger.getSourceState(), properties)
                : null;
        return trigger.toBuilder()
                .event(resolvedEvent != null
                        ? MachineEnumCanonicalizer.qualifyEventIdentifier(
                                resolvedEvent, trigger.getEventTypeFqn(), context)
                        : null)
                .sourceState(resolvedSource != null
                        ? MachineEnumCanonicalizer.canonicalizeLabel(
                                resolvedSource, trigger.getStateTypeFqn(), context)
                        : null)
                .polymorphicEvents(polymorphicEvents)
                .build();
    }

    private static CallChain resolveCallChain(
            CallChain chain,
            click.kamil.springstatemachineexporter.analysis.resolver.PropertyResolver resolver,
            Map<String, String> properties,
            CodebaseContext context) {
        TriggerPoint trigger = chain.getTriggerPoint() == null
                ? null
                : resolveTrigger(chain.getTriggerPoint(), resolver, properties, context);
        List<MatchedTransition> matchedTransitions = chain.getMatchedTransitions() == null ? null
                : chain.getMatchedTransitions().stream()
                        .map(matched -> MatchedTransition.builder()
                                .event(resolver.resolveValue(matched.getEvent(), properties))
                                .sourceState(resolver.resolveValue(matched.getSourceState(), properties))
                                .targetState(resolver.resolveValue(matched.getTargetState(), properties))
                                .build())
                        .collect(java.util.stream.Collectors.toList());
        return chain.toBuilder()
                .triggerPoint(trigger)
                .matchedTransitions(matchedTransitions)
                .build();
    }

    public void addMetadata(CodebaseMetadata newMetadata) {
        if (newMetadata == null) return;
        
        List<TriggerPoint> combinedTriggers = new java.util.ArrayList<>(this.metadata.getTriggers());
        if (newMetadata.getTriggers() != null) combinedTriggers.addAll(newMetadata.getTriggers());

        List<EntryPoint> combinedEntryPoints = new java.util.ArrayList<>(this.metadata.getEntryPoints());
        if (newMetadata.getEntryPoints() != null) combinedEntryPoints.addAll(newMetadata.getEntryPoints());

        List<CallChain> combinedCallChains = new java.util.ArrayList<>(this.metadata.getCallChains());
        if (newMetadata.getCallChains() != null) combinedCallChains.addAll(newMetadata.getCallChains());

        java.util.Map<String, java.util.Map<String, String>> combinedProperties = new java.util.HashMap<>(this.metadata.getProperties());
        if (newMetadata.getProperties() != null) combinedProperties.putAll(newMetadata.getProperties());

        this.metadata = CodebaseMetadata.builder()
                .triggers(Collections.unmodifiableList(combinedTriggers))
                .entryPoints(Collections.unmodifiableList(combinedEntryPoints))
                .callChains(Collections.unmodifiableList(combinedCallChains))
                .properties(Collections.unmodifiableMap(combinedProperties))
                .build();
    }
}
