import assert from 'node:assert/strict';
import test from 'node:test';
import {
  buildChangeRateAndVolumeSpikeStockAlertCandidates,
  buildChangeRateThresholdStockAlertLines,
  buildCurrentPriceStockAlertLines,
  buildStockAlertNotificationIdentity,
  resolveLatestQuoteFromMeta,
  resolveLatestQuoteFromNaverRealtime,
  resolveVolumeRate5dFromHistory,
  type StockAlertItem,
} from './stock-alert-service.js';

test('resolveLatestQuoteFromMeta prefers post-market quote outside regular session', () => {
  const quote = resolveLatestQuoteFromMeta({
    regularMarketPrice: 210000,
    regularMarketTime: 1714377600,
    regularMarketChangePercent: 1.23,
    previousClose: 207450,
    postMarketPrice: 211500,
    postMarketTime: 1714381200,
    postMarketChangePercent: 1.95,
    marketState: 'POST',
    shortName: '삼성전자',
  });

  assert.equal(quote.currentPrice, 211500);
  assert.equal(quote.changeRate, 1.95);
  assert.equal(quote.stockName, '삼성전자');
  assert.equal(quote.quotedAt, '2024-04-29T09:00:00.000Z');
});

test('buildCurrentPriceStockAlertLines formats current-price stock alert lines', () => {
  const items: StockAlertItem[] = [
    {
      id: '005930',
      stockCode: '005930',
      stockName: '삼성전자',
      alertTypes: ['price'],
      alertTypeLabels: ['현재가'],
      currentPrice: 210000,
      changeRate: 1.23,
      volumeRate5d: null,
      currentVolume: null,
      quotedAt: '2026-04-29T09:00:00.000Z',
      createdAt: '2026-04-29T09:00:00.000Z',
      updatedAt: '2026-04-29T09:00:00.000Z',
    },
    {
      id: '000660',
      stockCode: '000660',
      stockName: 'SK하이닉스',
      alertTypes: ['price'],
      alertTypeLabels: ['현재가'],
      currentPrice: 198000,
      changeRate: -2.34,
      volumeRate5d: null,
      currentVolume: null,
      quotedAt: '2026-04-29T09:00:00.000Z',
      createdAt: '2026-04-29T09:00:00.000Z',
      updatedAt: '2026-04-29T09:00:00.000Z',
    },
  ];

  assert.deepEqual(buildCurrentPriceStockAlertLines(items), [
    '삼성전자 210,000₩ (+1.23% ▲)',
    'SK하이닉스 198,000₩ (-2.34% ▼)',
  ]);
});

test('buildCurrentPriceStockAlertLines skips rows without resolved current-price quote', () => {
  const items: StockAlertItem[] = [
    {
      id: '005930',
      stockCode: '005930',
      stockName: '삼성전자',
      alertTypes: ['price'],
      alertTypeLabels: ['현재가'],
      currentPrice: 210000,
      changeRate: 1.23,
      volumeRate5d: null,
      currentVolume: null,
      quotedAt: '2026-04-29T09:00:00.000Z',
      createdAt: '2026-04-29T09:00:00.000Z',
      updatedAt: '2026-04-29T09:00:00.000Z',
    },
    {
      id: '035420',
      stockCode: '035420',
      stockName: 'NAVER',
      alertTypes: ['price'],
      alertTypeLabels: ['현재가'],
      currentPrice: null,
      changeRate: null,
      volumeRate5d: null,
      currentVolume: null,
      quotedAt: null,
      createdAt: '2026-04-29T09:00:00.000Z',
      updatedAt: '2026-04-29T09:00:00.000Z',
    },
  ];

  assert.deepEqual(buildCurrentPriceStockAlertLines(items), ['삼성전자 210,000₩ (+1.23% ▲)']);
});

test('buildCurrentPriceStockAlertLines excludes stocks not registered for current-price alerts', () => {
  const items: StockAlertItem[] = [
    {
      id: '005930',
      stockCode: '005930',
      stockName: '삼성전자',
      alertTypes: ['price', 'top3'],
      alertTypeLabels: ['현재가', '등락폭이 큰 상위3종목'],
      currentPrice: 210000,
      changeRate: 1.23,
      volumeRate5d: null,
      currentVolume: null,
      quotedAt: '2026-04-29T09:00:00.000Z',
      createdAt: '2026-04-29T09:00:00.000Z',
      updatedAt: '2026-04-29T09:00:00.000Z',
    },
    {
      id: '035420',
      stockCode: '035420',
      stockName: 'NAVER',
      alertTypes: ['top3'],
      alertTypeLabels: ['등락폭이 큰 상위3종목'],
      currentPrice: 230000,
      changeRate: 3.45,
      volumeRate5d: null,
      currentVolume: null,
      quotedAt: '2026-04-29T09:00:00.000Z',
      createdAt: '2026-04-29T09:00:00.000Z',
      updatedAt: '2026-04-29T09:00:00.000Z',
    },
  ];

  assert.deepEqual(buildCurrentPriceStockAlertLines(items), ['삼성전자 210,000₩ (+1.23% ▲)']);
});

test('buildChangeRateThresholdStockAlertLines includes every registered stock that crosses the threshold and sorts by absolute rate', () => {
  const items: StockAlertItem[] = [
    {
      id: '290550',
      stockCode: '290550',
      stockName: '디케이티',
      alertTypes: ['top3'],
      alertTypeLabels: ['등락폭이 큰 상위3종목'],
      currentPrice: 26500,
      changeRate: 11.11,
      volumeRate5d: null,
      currentVolume: null,
      quotedAt: '2026-04-29T09:00:00.000Z',
      createdAt: '2026-04-29T09:00:00.000Z',
      updatedAt: '2026-04-29T09:00:00.000Z',
    },
    {
      id: '005930',
      stockCode: '005930',
      stockName: '삼성전자',
      alertTypes: ['price'],
      alertTypeLabels: ['현재가'],
      currentPrice: 210000,
      changeRate: 6.1,
      volumeRate5d: null,
      currentVolume: null,
      quotedAt: '2026-04-29T09:00:00.000Z',
      createdAt: '2026-04-29T09:00:00.000Z',
      updatedAt: '2026-04-29T09:00:00.000Z',
    },
    {
      id: '066570',
      stockCode: '066570',
      stockName: 'LG전자',
      alertTypes: ['top3'],
      alertTypeLabels: ['등락폭이 큰 상위3종목'],
      currentPrice: 137400,
      changeRate: -1.86,
      volumeRate5d: null,
      currentVolume: null,
      quotedAt: '2026-04-29T09:00:00.000Z',
      createdAt: '2026-04-29T09:00:00.000Z',
      updatedAt: '2026-04-29T09:00:00.000Z',
    },
    {
      id: '035420',
      stockCode: '035420',
      stockName: 'NAVER',
      alertTypes: ['top3'],
      alertTypeLabels: ['등락폭이 큰 상위3종목'],
      currentPrice: 230000,
      changeRate: -7.23,
      volumeRate5d: null,
      currentVolume: null,
      quotedAt: '2026-04-29T09:00:00.000Z',
      createdAt: '2026-04-29T09:00:00.000Z',
      updatedAt: '2026-04-29T09:00:00.000Z',
    },
  ];

  assert.deepEqual(buildChangeRateThresholdStockAlertLines(items, 5), [
    '디케이티 26,500₩ (+11.11% ▲)',
    'NAVER 230,000₩ (-7.23% ▼)',
    '삼성전자 210,000₩ (+6.10% ▲)',
  ]);
});

test('buildChangeRateAndVolumeSpikeStockAlertCandidates keeps only stocks whose current jump beats the recent 5-record max by the configured ratio', () => {
  const items: StockAlertItem[] = [
    {
      id: '290550',
      stockCode: '290550',
      stockName: '디케이티',
      alertTypes: ['top3'],
      alertTypeLabels: ['등락폭이 큰 상위3종목'],
      currentPrice: 26500,
      changeRate: 11.11,
      volumeRate5d: null,
      currentVolume: 400000,
      quotedAt: '2026-05-06T00:30:00.000Z',
      createdAt: '2026-05-06T00:30:00.000Z',
      updatedAt: '2026-05-06T00:30:00.000Z',
    },
    {
      id: '005930',
      stockCode: '005930',
      stockName: '삼성전자',
      alertTypes: ['price'],
      alertTypeLabels: ['현재가'],
      currentPrice: 210000,
      changeRate: 6.1,
      volumeRate5d: null,
      currentVolume: 390000,
      quotedAt: '2026-05-06T00:30:00.000Z',
      createdAt: '2026-05-06T00:30:00.000Z',
      updatedAt: '2026-05-06T00:30:00.000Z',
    },
    {
      id: '035420',
      stockCode: '035420',
      stockName: 'NAVER',
      alertTypes: ['top3'],
      alertTypeLabels: ['등락폭이 큰 상위3종목'],
      currentPrice: 230000,
      changeRate: -7.23,
      volumeRate5d: null,
      currentVolume: 380000,
      quotedAt: '2026-05-06T00:30:00.000Z',
      createdAt: '2026-05-06T00:30:00.000Z',
      updatedAt: '2026-05-06T00:30:00.000Z',
    },
  ];

  const candidates = buildChangeRateAndVolumeSpikeStockAlertCandidates(
    items,
    new Map([
      [
        '290550',
        {
          stockCode: '290550',
          stockName: '디케이티',
          previousVolume: 100000,
          currentVolume: 100000,
          volumeIncreasePercent: 0,
          currentPrice: 25000,
          changeRate: 3,
          quotedAt: '2026-05-06T00:00:00.000Z',
          createdAt: '2026-05-06T00:00:00.000Z',
          updatedAt: '2026-05-06T00:00:00.000Z',
        },
      ],
      [
        '005930',
        {
          stockCode: '005930',
          stockName: '삼성전자',
          previousVolume: 130000,
          currentVolume: 130000,
          volumeIncreasePercent: 0,
          currentPrice: 205000,
          changeRate: 2,
          quotedAt: '2026-05-06T00:00:00.000Z',
          createdAt: '2026-05-06T00:00:00.000Z',
          updatedAt: '2026-05-06T00:00:00.000Z',
        },
      ],
      [
        '035420',
        {
          stockCode: '035420',
          stockName: 'NAVER',
          previousVolume: 120000,
          currentVolume: 120000,
          volumeIncreasePercent: 0,
          currentPrice: 240000,
          changeRate: -3,
          quotedAt: '2026-05-06T00:00:00.000Z',
          createdAt: '2026-05-06T00:00:00.000Z',
          updatedAt: '2026-05-06T00:00:00.000Z',
        },
      ],
    ]),
    new Map([
      [
        '290550',
        [
          {
            id: '290550:1',
            stockCode: '290550',
            stockName: '디케이티',
            baselineVolume: 100000,
            currentVolume: 160000,
            volumeIncreasePercent: 60,
            currentPrice: 25000,
            changeRate: 3,
            quotedAt: '2026-05-05T00:00:00.000Z',
            createdAt: '2026-05-05T00:00:00.000Z',
          },
        ],
      ],
      [
        '005930',
        [
          {
            id: '005930:1',
            stockCode: '005930',
            stockName: '삼성전자',
            baselineVolume: 130000,
            currentVolume: 200000,
            volumeIncreasePercent: 53.85,
            currentPrice: 205000,
            changeRate: 2,
            quotedAt: '2026-05-05T00:00:00.000Z',
            createdAt: '2026-05-05T00:00:00.000Z',
          },
        ],
      ],
      [
        '035420',
        [
          {
            id: '035420:1',
            stockCode: '035420',
            stockName: 'NAVER',
            baselineVolume: 120000,
            currentVolume: 190000,
            volumeIncreasePercent: 58.33,
            currentPrice: 240000,
            changeRate: -3,
            quotedAt: '2026-05-05T00:00:00.000Z',
            createdAt: '2026-05-05T00:00:00.000Z',
          },
        ],
      ],
    ]),
    {
      thresholdPercent: 3,
      minVolumeIncreasePercent: 300,
    },
  );

  assert.deepEqual(candidates, [
    {
      stockCode: '290550',
      stockName: '디케이티',
      currentPrice: 26500,
      changeRate: 11.11,
      currentVolume: 400000,
      previousVolume: 100000,
      volumeIncreasePercent: 300,
      recentMaxVolumeIncreasePercent: 60,
      volumeAmplificationGrowthPercent: 400,
      quotedAt: '2026-05-06T00:30:00.000Z',
    },
  ]);
});

test('buildChangeRateAndVolumeSpikeStockAlertCandidates treats 100% as a doubled amplification amount', () => {
  const items: StockAlertItem[] = [
    {
      id: '290550',
      stockCode: '290550',
      stockName: '디케이티',
      alertTypes: ['top3'],
      alertTypeLabels: ['등락폭이 큰 상위3종목'],
      currentPrice: 26500,
      changeRate: 11.11,
      volumeRate5d: null,
      currentVolume: 350000,
      quotedAt: '2026-05-06T00:30:00.000Z',
      createdAt: '2026-05-06T00:30:00.000Z',
      updatedAt: '2026-05-06T00:30:00.000Z',
    },
  ];

  const candidates = buildChangeRateAndVolumeSpikeStockAlertCandidates(
    items,
    new Map([
      [
        '290550',
        {
          stockCode: '290550',
          stockName: '디케이티',
          previousVolume: 100000,
          currentVolume: 200000,
          volumeIncreasePercent: 100,
          currentPrice: 25000,
          changeRate: 3,
          quotedAt: '2026-05-06T00:00:00.000Z',
          createdAt: '2026-05-06T00:00:00.000Z',
          updatedAt: '2026-05-06T00:00:00.000Z',
        },
      ],
    ]),
    new Map([
      [
        '290550',
        [
          {
            id: '290550:1',
            stockCode: '290550',
            stockName: '디케이티',
            baselineVolume: 100000,
            currentVolume: 150000,
            volumeIncreasePercent: 50,
            currentPrice: 25000,
            changeRate: 3,
            quotedAt: '2026-05-05T00:00:00.000Z',
            createdAt: '2026-05-05T00:00:00.000Z',
          },
        ],
      ],
    ]),
    {
      thresholdPercent: 3,
      minVolumeIncreasePercent: 50,
    },
  );

  assert.deepEqual(candidates, [
    {
      stockCode: '290550',
      stockName: '디케이티',
      currentPrice: 26500,
      changeRate: 11.11,
      currentVolume: 350000,
      previousVolume: 200000,
      volumeIncreasePercent: 75,
      recentMaxVolumeIncreasePercent: 50,
      volumeAmplificationGrowthPercent: 50,
      quotedAt: '2026-05-06T00:30:00.000Z',
    },
  ]);
});

test('buildChangeRateAndVolumeSpikeStockAlertCandidates skips stocks without a previous batch snapshot baseline', () => {
  const items: StockAlertItem[] = [
    {
      id: '290550',
      stockCode: '290550',
      stockName: '디케이티',
      alertTypes: ['top3'],
      alertTypeLabels: ['등락폭이 큰 상위3종목'],
      currentPrice: 26500,
      changeRate: 11.11,
      volumeRate5d: 400,
      currentVolume: 400000,
      quotedAt: '2026-05-06T00:30:00.000Z',
      createdAt: '2026-05-06T00:30:00.000Z',
      updatedAt: '2026-05-06T00:30:00.000Z',
    },
    {
      id: '035420',
      stockCode: '035420',
      stockName: 'NAVER',
      alertTypes: ['top3'],
      alertTypeLabels: ['등락폭이 큰 상위3종목'],
      currentPrice: 230000,
      changeRate: -7.23,
      volumeRate5d: 250,
      currentVolume: 380000,
      quotedAt: '2026-05-06T00:30:00.000Z',
      createdAt: '2026-05-06T00:30:00.000Z',
      updatedAt: '2026-05-06T00:30:00.000Z',
    },
  ];

  const candidates = buildChangeRateAndVolumeSpikeStockAlertCandidates(
    items,
    new Map(),
    new Map([
      [
        '290550',
        [
          {
            id: '290550:1',
            stockCode: '290550',
            stockName: '디케이티',
            baselineVolume: 100000,
            currentVolume: 175000,
            volumeIncreasePercent: 75,
            currentPrice: 25000,
            changeRate: 3,
            quotedAt: '2026-05-05T00:00:00.000Z',
            createdAt: '2026-05-05T00:00:00.000Z',
          },
        ],
      ],
    ]),
    {
      thresholdPercent: 3,
      minVolumeIncreasePercent: 300,
    },
  );

  assert.deepEqual(candidates, []);
});

test('buildStockAlertNotificationIdentity keeps one stable notification per schedule and preserves legacy aliases', () => {
  assert.deepEqual(
    buildStockAlertNotificationIdentity({
      scheduleId: 2,
      serviceKey: 'schedule-2-stock-alert-service',
      mode: 'price',
    }),
    {
      threadId: 'schedule-stock-alert:2',
      notificationKey: 'schedule-stock-alert:2',
      notificationScope: 'schedule-stock-alert:2',
      notificationAliases: [
        'schedule-2-stock-alert-service',
        'schedule-2-stock-alert-service:current-price',
        'schedule-2-stock-alert',
        'schedule-2-stock-alert:current-price',
      ],
    },
  );

  assert.deepEqual(
    buildStockAlertNotificationIdentity({
      scheduleId: 10,
      serviceKey: 'schedule-10-stock-high-service',
      mode: 'change-threshold',
    }),
    {
      threadId: 'schedule-stock-alert:10',
      notificationKey: 'schedule-stock-alert:10',
      notificationScope: 'schedule-stock-alert:10',
      notificationAliases: [
        'schedule-10-stock-high-service',
        'schedule-10-stock-high-service:current-price',
        'schedule-10-stock-high-service:change-threshold',
      ],
    },
  );

  assert.deepEqual(
    buildStockAlertNotificationIdentity({
      scheduleId: 11,
      serviceKey: 'schedule-11-stock-hight-v2-service',
      mode: 'change-threshold-volume-spike',
    }),
    {
      threadId: 'schedule-stock-alert:11',
      notificationKey: 'schedule-stock-alert:11',
      notificationScope: 'schedule-stock-alert:11',
      notificationAliases: [
        'schedule-11-stock-hight-v2-service',
        'schedule-11-stock-hight-v2-service:current-price',
        'schedule-11-stock-hight-v2-service:change-threshold-volume-spike',
      ],
    },
  );
});

test('resolveLatestQuoteFromNaverRealtime prefers extended-hours quote when available', () => {
  const quote = resolveLatestQuoteFromNaverRealtime(
    {
      cd: '005930',
      nm: '삼성전자',
      nv: 226000,
      cr: 1.8,
      nxtOverMarketPriceInfo: {
        tradingSessionType: 'AFTER_MARKET',
        overMarketStatus: 'OPEN',
        overPrice: '225,500',
        fluctuationsRatio: '1.58',
        localTradedAt: '2026-04-29T19:05:12.465411+09:00',
      },
    },
    1777457112465,
  );

  assert.equal(quote.currentPrice, 225500);
  assert.equal(quote.changeRate, 1.58);
  assert.equal(quote.currentVolume, null);
  assert.equal(quote.stockName, '삼성전자');
  assert.equal(quote.quotedAt, '2026-04-29T19:05:12.465411+09:00');
});

test('resolveLatestQuoteFromNaverRealtime keeps accumulated trading volume for volume-rate calculation', () => {
  const quote = resolveLatestQuoteFromNaverRealtime(
    {
      cd: '005930',
      nm: '삼성전자',
      nv: 224750,
      cr: 0.55,
      aq: '11,342,102',
    },
    1777521720275,
  );

  assert.equal(quote.currentVolume, 11342102);
});

test('resolveVolumeRate5dFromHistory prefers realtime current volume over the last daily candle', () => {
  const rate = resolveVolumeRate5dFromHistory(11342102, [34525485, 19626666, 22870374, 18444490, 20363756, 11015729]);

  assert.equal(rate?.toFixed(2), '48.96');
});

test('resolveLatestQuoteFromNaverRealtime keeps after-hours quote even after session close', () => {
  const quote = resolveLatestQuoteFromNaverRealtime(
    {
      cd: '066570',
      nm: 'LG전자',
      nv: 135800,
      cr: 3,
      nxtOverMarketPriceInfo: {
        tradingSessionType: 'AFTER_MARKET',
        overMarketStatus: 'CLOSE',
        overPrice: '137,400',
        compareToPreviousClosePrice: '-2,600',
        fluctuationsRatio: '-1.86',
        localTradedAt: '2026-04-29T20:00:00.000000+09:00',
      },
    },
    1777461821319,
  );

  assert.equal(quote.currentPrice, 137400);
  assert.equal(quote.changeRate, -1.86);
  assert.equal(quote.stockName, 'LG전자');
  assert.equal(quote.quotedAt, '2026-04-29T20:00:00.000000+09:00');
});

test('resolveLatestQuoteFromNaverRealtime resets domestic quote to previous close from 5AM before premarket starts', () => {
  const quote = resolveLatestQuoteFromNaverRealtime(
    {
      cd: '005930',
      nm: '삼성전자',
      nv: 226000,
      cr: 1.8,
      ms: 'CLOSE',
      pcv: 222000,
    },
    '2026-04-30T05:30:00+09:00',
  );

  assert.equal(quote.currentPrice, 222000);
  assert.equal(quote.changeRate, 0);
  assert.equal(quote.stockName, '삼성전자');
  assert.equal(quote.quotedAt, '2026-04-29T20:30:00.000Z');
});

test('resolveLatestQuoteFromNaverRealtime keeps before-market quote when available in the morning reset window', () => {
  const quote = resolveLatestQuoteFromNaverRealtime(
    {
      cd: '005930',
      nm: '삼성전자',
      nv: 226000,
      cr: 1.8,
      ms: 'CLOSE',
      pcv: 222000,
      nxtOverMarketPriceInfo: {
        tradingSessionType: 'BEFORE_MARKET',
        overMarketStatus: 'OPEN',
        overPrice: '223,500',
        fluctuationsRatio: '0.68',
        compareToPreviousClosePrice: '1,500',
        localTradedAt: '2026-04-30T08:10:00+09:00',
      },
    },
    '2026-04-30T08:10:05+09:00',
  );

  assert.equal(quote.currentPrice, 223500);
  assert.equal(quote.changeRate, 0.68);
  assert.equal(quote.stockName, '삼성전자');
  assert.equal(quote.quotedAt, '2026-04-30T08:10:00+09:00');
});

test('resolveLatestQuoteFromNaverRealtime restores negative sign when ratio arrives unsigned', () => {
  const quote = resolveLatestQuoteFromNaverRealtime(
    {
      cd: '066570',
      nm: 'LG전자',
      nv: 135800,
      cr: 3,
      nxtOverMarketPriceInfo: {
        tradingSessionType: 'AFTER_MARKET',
        overMarketStatus: 'CLOSE',
        overPrice: '137,400',
        compareToPreviousClosePrice: '-2,600',
        fluctuationsRatio: '1.86',
        compareToPreviousPrice: {
          code: '5',
          name: 'FALLING',
        },
        localTradedAt: '2026-04-29T20:00:00.000000+09:00',
      },
    },
    1777461821319,
  );

  assert.equal(quote.currentPrice, 137400);
  assert.equal(quote.changeRate, -1.86);
  assert.equal(quote.stockName, 'LG전자');
});

test('resolveLatestQuoteFromNaverRealtime falls back to regular quote when extended-hours quote is unavailable', () => {
  const quote = resolveLatestQuoteFromNaverRealtime(
    {
      cd: '005930',
      nm: '삼성전자',
      nv: 226000,
      cr: 1.8,
    },
    1777457112465,
  );

  assert.equal(quote.currentPrice, 226000);
  assert.equal(quote.changeRate, 1.8);
  assert.equal(quote.stockName, '삼성전자');
  assert.equal(quote.quotedAt, '2026-04-29T10:05:12.465Z');
});

test('resolveLatestQuoteFromNaverRealtime restores negative sign for regular-session quotes from rf direction', () => {
  const quote = resolveLatestQuoteFromNaverRealtime(
    {
      cd: '024950',
      nm: '삼천리자전거',
      nv: 5130,
      cr: 2.47,
      rf: '5',
      pcv: 5260,
    },
    '2026-04-30T14:13:05+09:00',
  );

  assert.equal(quote.currentPrice, 5130);
  assert.equal(quote.changeRate, -2.47);
  assert.equal(quote.stockName, '삼천리자전거');
  assert.equal(quote.quotedAt, '2026-04-30T05:13:05.000Z');
});
