package com.r35157.jupiterperpsalarm.impl.ref;

import java.time.Instant;

public final class PriceAlarm {

    public PriceAlarm(
            PriceAlarmDefinition definition,
            AlarmVariableResolver variableResolver,
            AlarmAction action
    ) {
        this.definition = definition;
        this.variableResolver = variableResolver;
        this.action = action;
    }

    public synchronized void accept(OraclePrice price) {
        if (price.asset() != definition.asset()) {
            throw new IllegalArgumentException(
                    "Received " + price.asset() + " price for " + definition.asset() + " alarm"
            );
        }

        PriceCondition condition;
        try {
            String resolvedConditionExpression = variableResolver.resolve(
                    definition.conditionExpression()
            );

            condition = definition.trigger() == AlarmTrigger.CROSSING
                    ? AlarmConditionParser.parseCrossing(resolvedConditionExpression)
                    : AlarmConditionParser.parse(resolvedConditionExpression);
        } catch (RuntimeException exception) {
            System.err.printf(
                    "Could not resolve condition for alarm %d: %s%n",
                    definition.id(),
                    exception.getMessage()
            );
            return;
        }

        if (definition.trigger() == AlarmTrigger.CROSSING) {
            acceptCrossing(price, (CrossingPriceCondition) condition);
            return;
        }

        boolean reached = condition.matches(price.priceUsd());

        boolean enteredTriggeredSide = previousReached == null
                ? reached
                : reached && !previousReached;

        previousReached = reached;

        if (!reached) {
            previousReached = false;
            return;
        }

        if (definition.trigger() == AlarmTrigger.ONETIME) {
            if (!enteredTriggeredSide || triggerCount > 0) {
                return;
            }

            trigger(price, condition);
            return;
        }

        if (definition.trigger() == AlarmTrigger.PERSISTENT) {
            if (lastTriggeredAt == null || persistentGracePeriodHasPassed()) {
                trigger(price, condition);
            }
            return;
        }

        throw new IllegalStateException("Unsupported alarm trigger: " + definition.trigger());
    }

    private void acceptCrossing(
            OraclePrice price,
            CrossingPriceCondition condition
    ) {
        boolean reached = condition.matches(price.priceUsd());

        if (crossingArmed == null) {
            crossingArmed = !reached;
            return;
        }

        if (crossingArmed) {
            if (reached) {
                crossingArmed = false;
                trigger(price, condition);
            }
            return;
        }

        if (condition.matchesRearmCondition(price.priceUsd())) {
            crossingArmed = true;
        }
    }

    public PriceAlarmDefinition definition() {
        return definition;
    }

    public synchronized long triggerCount() {
        return triggerCount;
    }

    private boolean persistentGracePeriodHasPassed() {
        if (lastTriggeredAt == null) {
            return true;
        }

        ΩmilliSecondsΩ gracePeriod = definition.triggerGracePeriod();

        if (gracePeriod == 0) {
            return true;
        }

        return !Instant.now().isBefore(
                lastTriggeredAt.plusMillis(gracePeriod)
        );
    }

    private void trigger(OraclePrice price, PriceCondition condition) {
        triggerCount++;
        lastTriggeredAt = Instant.now();

        ResolvedPriceAlarm resolvedAlarm = new ResolvedPriceAlarm(
                definition.id(),
                definition.asset(),
                condition,
                definition.trigger(),
                definition.triggerGracePeriod()
        );

        action.trigger(price, resolvedAlarm);
    }

    private final PriceAlarmDefinition definition;
    private final AlarmVariableResolver variableResolver;
    private final AlarmAction action;

    private Instant lastTriggeredAt;
    private Boolean crossingArmed;
    private Boolean previousReached;
    private long triggerCount;
}
