package com.goldenchart.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.goldenchart.storage.Ta4jStorage;
import com.goldenchart.trading.pipeline.TradingPipelineProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.ta4j.core.BarSeries;
import org.ta4j.core.BaseBarSeriesBuilder;
import org.ta4j.core.Rule;
import org.ta4j.core.TradingRecord;
import org.ta4j.core.bars.TimeBarBuilderFactory;
import org.ta4j.core.num.DoubleNumFactory;

import java.time.Duration;
import java.time.Instant;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 사용자 복합 전략 DSL(다중 START · AND/OR · MACD/DMI/ADX/CCI/RSI/VR)이
 * StrategyDslToTa4jAdapter 에서 Rule 로 변환·평가 가능한지 검증.
 */
class ComplexStrategyDslAdapterTest {

    private static final String MARKET = "KRW-BTC";
    private static final ObjectMapper MAPPER = new ObjectMapper();

    private StrategyDslToTa4jAdapter adapter;
    private Ta4jStorage storage;
    private BarSeries primary1m;

    @BeforeEach
    void setUp() {
        adapter = new StrategyDslToTa4jAdapter();
        TradingPipelineProperties props = new TradingPipelineProperties();
        props.setEnabled(false);
        storage = new Ta4jStorage(props);
        primary1m = buildSyntheticSeries("1m", 260);
        seedStorage(MARKET, "1m", primary1m);
        seedStorage(MARKET, "3m", buildSyntheticSeries("3m", 120));
        seedStorage(MARKET, "5m", buildSyntheticSeries("5m", 80));
    }

  /** Logic Expression 예시와 동일 구조의 매수 조건 */
    @Test
    void buyCondition_compilesAndEvaluates() throws Exception {
        JsonNode buy = MAPPER.readTree("""
            {
              "id": "buy-root",
              "type": "OR",
              "children": [
                {
                  "id": "buy-and-1",
                  "type": "AND",
                  "children": [
                    {
                      "id": "c-macd",
                      "type": "CONDITION",
                      "condition": {
                        "indicatorType": "MACD",
                        "conditionType": "CROSS_UP",
                        "leftField": "MACD_LINE",
                        "rightField": "SIGNAL_LINE",
                        "candleRange": 1,
                        "period": 12
                      }
                    },
                    {
                      "id": "c-dmi",
                      "type": "CONDITION",
                      "condition": {
                        "indicatorType": "DMI",
                        "conditionType": "CROSS_UP",
                        "leftField": "PDI",
                        "rightField": "MDI",
                        "candleRange": 1,
                        "period": 14
                      }
                    }
                  ]
                },
                {
                  "id": "buy-and-2",
                  "type": "AND",
                  "children": [
                    {
                      "id": "c-adx",
                      "type": "CONDITION",
                      "condition": {
                        "indicatorType": "ADX",
                        "conditionType": "CROSS_UP",
                        "leftField": "ADX_VALUE",
                        "rightField": "K_25",
                        "candleRange": 1,
                        "period": 14
                      }
                    },
                    {
                      "id": "c-cci",
                      "type": "CONDITION",
                      "condition": {
                        "indicatorType": "CCI",
                        "conditionType": "CROSS_UP",
                        "leftField": "CCI_VALUE",
                        "rightField": "K_100",
                        "candleRange": 1,
                        "period": 13
                      }
                    }
                  ]
                }
              ]
            }
            """);

        Rule rule = adapter.toRule(buy, primary1m, Map.of(), MARKET, storage);
        assertNotNull(rule);
        assertDoesNotThrow(() -> evaluateRange(rule, primary1m.getEndIndex()));
    }

    /** Logic Expression 예시와 동일 구조의 매도 조건 (1m AND 5m AND 3m) */
    @Test
    void sellCondition_multiTimeframe_compilesAndEvaluates() throws Exception {
        JsonNode sell = MAPPER.readTree("""
            {
              "id": "sell-root",
              "type": "AND",
              "children": [
                {
                  "id": "tf-1m",
                  "type": "TIMEFRAME",
                  "candleType": "1m",
                  "children": [{
                    "id": "sell-1m-and",
                    "type": "AND",
                    "children": [
                      {
                        "id": "c-rsi-1m",
                        "type": "CONDITION",
                        "condition": {
                          "indicatorType": "RSI",
                          "conditionType": "CROSS_DOWN",
                          "leftField": "RSI_VALUE",
                          "rightField": "K_70",
                          "candleRange": 1,
                          "period": 9
                        }
                      },
                      {
                        "id": "c-macd-1m",
                        "type": "CONDITION",
                        "condition": {
                          "indicatorType": "MACD",
                          "conditionType": "CROSS_DOWN",
                          "leftField": "MACD_LINE",
                          "rightField": "SIGNAL_LINE",
                          "candleRange": 1,
                          "period": 12
                        }
                      },
                      {
                        "id": "c-vr-1m",
                        "type": "CONDITION",
                        "condition": {
                          "indicatorType": "VR",
                          "conditionType": "CROSS_DOWN",
                          "leftField": "VR_VALUE",
                          "rightField": "K_200",
                          "candleRange": 1,
                          "period": 10
                        }
                      }
                    ]
                  }]
                },
                {
                  "id": "tf-5m",
                  "type": "TIMEFRAME",
                  "candleType": "5m",
                  "children": [{
                    "id": "sell-5m-and",
                    "type": "AND",
                    "children": [
                      {
                        "id": "c-adx-5m",
                        "type": "CONDITION",
                        "condition": {
                          "indicatorType": "ADX",
                          "conditionType": "CROSS_DOWN",
                          "leftField": "ADX_VALUE",
                          "rightField": "K_28",
                          "candleRange": 1,
                          "period": 14
                        }
                      },
                      {
                        "id": "c-cci-5m",
                        "type": "CONDITION",
                        "condition": {
                          "indicatorType": "CCI",
                          "conditionType": "CROSS_DOWN",
                          "leftField": "CCI_VALUE",
                          "rightField": "K_26",
                          "candleRange": 1,
                          "period": 13
                        }
                      }
                    ]
                  }]
                },
                {
                  "id": "tf-3m",
                  "type": "TIMEFRAME",
                  "candleType": "3m",
                  "children": [{
                    "id": "sell-3m-and",
                    "type": "AND",
                    "children": [
                      {
                        "id": "c-rsi-3m",
                        "type": "CONDITION",
                        "condition": {
                          "indicatorType": "RSI",
                          "conditionType": "CROSS_DOWN",
                          "leftField": "RSI_VALUE",
                          "rightField": "K_20",
                          "candleRange": 1,
                          "period": 9
                        }
                      },
                      {
                        "id": "c-vr-3m",
                        "type": "CONDITION",
                        "condition": {
                          "indicatorType": "VR",
                          "conditionType": "CROSS_DOWN",
                          "leftField": "VR_VALUE",
                          "rightField": "K_20",
                          "candleRange": 1,
                          "period": 10
                        }
                      }
                    ]
                  }]
                }
              ]
            }
            """);

        Rule rule = adapter.toRule(sell, primary1m, Map.of(), MARKET, storage);
        assertNotNull(rule);
        assertDoesNotThrow(() -> evaluateRange(rule, primary1m.getEndIndex()));

        // CrossSeriesRule: 1m 트리거 인덱스에서 5m·3m 시리즈 endIndex 로 평가
        int idx = primary1m.getEndIndex();
        boolean result = rule.isSatisfied(idx, null);
        assertTrue(result || !result, "다중 시간봉 AND Rule 평가가 예외 없이 boolean 반환");
    }

    /** 백테스트 경로( storage 미전달 ) — 단일 시리즈 fallback 동작 확인 */
    @Test
    void sellCondition_backtestSingleSeries_fallback() throws Exception {
        JsonNode sell = MAPPER.readTree("""
            {
              "type": "AND",
              "children": [
                { "type": "TIMEFRAME", "candleType": "1m", "children": [
                  { "type": "CONDITION", "condition": {
                    "indicatorType": "RSI", "conditionType": "CROSS_DOWN",
                    "leftField": "RSI_VALUE", "rightField": "K_70", "period": 9
                  }}
                ]},
                { "type": "TIMEFRAME", "candleType": "5m", "children": [
                  { "type": "CONDITION", "condition": {
                    "indicatorType": "ADX", "conditionType": "CROSS_DOWN",
                    "leftField": "ADX_VALUE", "rightField": "K_28", "period": 14
                  }}
                ]}
              ]
            }
            """);

        Rule rule = adapter.toRule(sell, primary1m, Map.of());
        assertNotNull(rule);
        assertDoesNotThrow(() -> rule.isSatisfied(primary1m.getEndIndex(), null));
    }

  // ── helpers ────────────────────────────────────────────────────────────────

    private static void evaluateRange(Rule rule, int endIndex) {
        TradingRecord record = null;
        int start = Math.max(30, endIndex - 20);
        for (int i = start; i <= endIndex; i++) {
            rule.isSatisfied(i, record);
        }
    }

    private static BarSeries buildSyntheticSeries(String timeframe, int barCount) {
        Duration period = switch (timeframe) {
            case "3m" -> Duration.ofMinutes(3);
            case "5m" -> Duration.ofMinutes(5);
            default -> Duration.ofMinutes(1);
        };
        BarSeries series = new BaseBarSeriesBuilder()
                .withName("test-" + timeframe)
                .withNumFactory(DoubleNumFactory.getInstance())
                .withBarBuilderFactory(new TimeBarBuilderFactory())
                .build();
        TimeBarBuilderFactory factory = new TimeBarBuilderFactory();
        Instant base = Instant.parse("2024-06-01T00:00:00Z");
        for (int i = 0; i < barCount; i++) {
            double wave = Math.sin(i * 0.07) * 8 + Math.cos(i * 0.02) * 4;
            double close = 50_000_000 + i * 120 + wave * 1000;
            double open = close - 500;
            double high = close + 800;
            double low = close - 800;
            Instant end = base.plus(period.multipliedBy(i + 1L));
            factory.createBarBuilder(series)
                    .timePeriod(period)
                    .endTime(end)
                    .openPrice(open).highPrice(high).lowPrice(low).closePrice(close)
                    .volume(12.5 + i * 0.01)
                    .add();
        }
        return series;
    }

    private void seedStorage(String market, String candleType, BarSeries source) {
        for (int i = source.getBeginIndex(); i <= source.getEndIndex(); i++) {
            storage.addBar(market, candleType, source.getBar(i));
        }
    }
}
