<script setup lang="ts">
import type { EchartsUIType } from '@vben/plugins/echarts';

import { computed, nextTick, onMounted, ref, watch } from 'vue';

import { EchartsUI, useEcharts } from '@vben/plugins/echarts';

import {
  Button,
  CheckboxGroup,
  Empty,
  Input,
  message,
  Pagination,
  Radio,
  RadioGroup,
  Switch,
  Spin,
} from 'ant-design-vue';
import dayjs from 'dayjs';

import { requestClient } from '#/api/request';

type MarketPoint = {
  origin?: string;
  source?: number;
  time: number;
  value: number;
};

type OddsHistory = {
  endTime: number;
  eventId: number;
  leagueName?: string;
  markets?: Record<string, MarketPoint[]>;
  startTime: number;
  teamAwayName?: string;
  teamHomeName?: string;
};

type HistoryGame = {
  endTime: number;
  eventId: number;
  leagueName?: string;
  marketCount: number;
  startTime: number;
  teamAwayName?: string;
  teamHomeName?: string;
  updatedAt?: string;
};

type HistoryGamesResponse = {
  list: HistoryGame[];
  page: number;
  pageSize: number;
  total: number;
};

const MARKET_KEYS = ['ior_ot_1', 'ior_ot_2', 'ior_ot_3', 'ior_ot_4'];
const PLATFORMS = [
  { label: 'PC', value: 'pc' },
  { label: 'OB', value: 'ob' },
];

const MARKET_LABELS: Record<string, string> = {
  ior_ot_1: '总进球 1',
  ior_ot_2: '总进球 2',
  ior_ot_3: '总进球 3',
  ior_ot_4: '总进球 4',
};

const getMarketKey = (platform: string, key: string) => `${platform}:${key}`;

const parseMarketKey = (key: string) => {
  if (MARKET_KEYS.includes(key)) {
    return { ior: key, platform: 'pc' };
  }
  const [platform, ior] = key.split(/[:.]/);
  if (PLATFORMS.some((item) => item.value === platform) && MARKET_KEYS.includes(ior)) {
    return { ior, platform };
  }
  return null;
};

const normalizeHistoryMarkets = (markets?: Record<string, MarketPoint[]>) => {
  const normalized: Record<string, MarketPoint[]> = {};
  Object.entries(markets ?? {}).forEach(([key, points]) => {
    const parsed = parseMarketKey(key);
    if (!parsed || !points?.length) {
      return;
    }
    normalized[getMarketKey(parsed.platform, parsed.ior)] = points;
  });
  return normalized;
};

const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

const games = ref<HistoryGame[]>([]);
const history = ref<OddsHistory | null>(null);
const selectedEventId = ref<number>();
const selectedMarkets = ref<string[]>([]);
const loadingGames = ref(false);
const loadingHistory = ref(false);
const isMultiSelect = ref(false);
const marketType = ref(-1);
const pageSize = 50;
const currentPage = ref(1);
const searchValue = ref('');
const searchTimer = ref<ReturnType<typeof setTimeout>>();
const totalGames = ref(0);

const availableMarketKeys = computed(() => {
  const markets = normalizeHistoryMarkets(history.value?.markets);
  return PLATFORMS.flatMap(({ value: platform }) => (
    MARKET_KEYS.map((key) => getMarketKey(platform, key))
  )).filter((key) => markets[key]?.length);
});

const hasChartData = computed(() => !!history.value && availableMarketKeys.value.length > 0);

const selectedGame = computed(() => {
  return games.value.find((item) => item.eventId === selectedEventId.value);
});

const historyTitle = computed(() => {
  const source = history.value ?? selectedGame.value;
  if (!source) {
    return '未选择比赛';
  }
  return `${source.teamHomeName ?? ''} vs ${source.teamAwayName ?? ''}`;
});

const isHalfEvent = (eventId?: number) => {
  return typeof eventId === 'number' && eventId < 0;
};

const marketOptionGroups = computed(() => {
  const available = new Set(availableMarketKeys.value);
  return PLATFORMS.map(({ label, value: platform }) => ({
    label,
    options: MARKET_KEYS.map((key) => {
      const value = getMarketKey(platform, key);
      return {
        label: MARKET_LABELS[key] ?? key,
        value,
      };
    }).filter((option) => available.has(option.value)),
  })).filter((group) => group.options.length);
});

const getGroupSelectedMarkets = (options: Array<{ value: string }>) => {
  const values = new Set(options.map((option) => option.value));
  return selectedMarkets.value.filter((key) => values.has(key));
};

const updateGroupSelectedMarkets = (options: Array<{ value: string }>, values: string[]) => {
  const groupValues = new Set(options.map((option) => option.value));
  selectedMarkets.value = [
    ...selectedMarkets.value.filter((key) => !groupValues.has(key)),
    ...values,
  ];
};

const selectedMarket = computed({
  get: () => selectedMarkets.value[0],
  set: (key?: string) => {
    selectedMarkets.value = key ? [key] : [];
  },
});

const formatTime = (time?: number) => {
  return time ? dayjs(time).format('MM-DD HH:mm:ss') : '-';
};

const formatGameTime = (time?: number) => {
  return time ? dayjs(time).format('MM-DD HH:mm') : '-';
};

const getMarketColor = (index: number) => {
  const colors = [
    '#1677ff',
    '#13a8a8',
    '#fa8c16',
    '#722ed1',
    '#eb2f96',
    '#52c41a',
    '#faad14',
    '#2f54eb',
    '#a0d911',
    '#f5222d',
    '#08979c',
    '#531dab',
  ];
  return colors[index % colors.length] ?? colors[0];
};

const getMarketLabel = (key: string) => {
  const [platform, ior] = key.includes(':') || key.includes('.') ? key.split(/[:.]/) : ['pc', key];
  const platformLabel = PLATFORMS.find((item) => item.value === platform)?.label ?? platform.toUpperCase();
  return `${platformLabel} ${MARKET_LABELS[ior] ?? ior}`;
};

const formatSeriesData = (points: MarketPoint[] = []) => {
  const data: Array<[number, null | number]> = [];
  let lastValue: number | null = null;

  points.forEach((point) => {
    if (point.value === 0) {
      if (lastValue !== null) {
        data.push([point.time, lastValue]);
      }
      data.push([point.time, null]);
      lastValue = null;
      return;
    }

    data.push([point.time, point.value]);
    lastValue = point.value;
  });

  return data;
};

const renderChart = () => {
  const markets = normalizeHistoryMarkets(history.value?.markets);
  const keys = selectedMarkets.value.filter((key) => markets[key]?.length);

  if (!keys.length) {
    renderEcharts({
      grid: { bottom: 40, containLabel: true, left: 32, right: 24, top: 32 },
      series: [],
      xAxis: { type: 'time' },
      yAxis: { type: 'value' },
    });
    return;
  }

  renderEcharts({
    dataZoom: [
      {
        bottom: 8,
        height: 22,
        type: 'slider',
      },
      {
        type: 'inside',
      },
    ],
    grid: {
      bottom: 66,
      containLabel: true,
      left: 24,
      right: 24,
      top: 36,
    },
    legend: {
      bottom: 34,
      type: 'scroll',
    },
    series: keys.map((key, index) => ({
      connectNulls: false,
      data: formatSeriesData(markets[key]),
      itemStyle: {
        color: getMarketColor(index),
      },
      lineStyle: {
        width: 1,
      },
      name: getMarketLabel(key),
      showSymbol: true,
      smooth: false,
      symbolSize: 2,
      type: 'line',
    })),
    tooltip: {
      trigger: 'axis',
      valueFormatter: (value) => {
        return typeof value === 'number' ? value.toFixed(3) : `${value ?? ''}`;
      },
    },
    xAxis: {
      axisLabel: {
        formatter: (value: number) => dayjs(value).format('HH:mm'),
      },
      max: history.value?.endTime,
      min: history.value ? history.value.startTime - 2 * 60 * 60 * 1000 : undefined,
      type: 'time',
    },
    yAxis: {
      min: 0,
      scale: true,
      splitLine: {
        lineStyle: {
          type: 'dashed',
        },
      },
      type: 'value',
    },
  });
};

const fetchGames = async () => {
  loadingGames.value = true;
  try {
    const data = await requestClient.get<HistoryGamesResponse>('/pstery/get_odds_history_games', {
      params: {
        keyword: searchValue.value.trim() || undefined,
        page: currentPage.value,
        page_size: pageSize,
        status: marketType.value,
      },
    });
    games.value = data?.list ?? [];
    totalGames.value = data?.total ?? 0;
    if (!selectedEventId.value && games.value.length) {
      selectedEventId.value = games.value[0]?.eventId;
    }
    if (selectedEventId.value) {
      await fetchHistory(selectedEventId.value);
    }
  }
  catch (error) {
    console.error('Failed to fetch games relation:', error);
    message.error('获取比赛列表失败');
  }
  finally {
    loadingGames.value = false;
  }
};

const fetchHistory = async (eventId: number) => {
  loadingHistory.value = true;
  try {
    const data = await requestClient.get<OddsHistory | null>('/pstery/get_odds_history', {
      params: { event_id: eventId },
    });
    history.value = data;
    const markets = normalizeHistoryMarkets(data?.markets);
    const firstKey = PLATFORMS.flatMap(({ value: platform }) => (
      MARKET_KEYS.map((key) => getMarketKey(platform, key))
    )).find((key) => markets[key]?.length);
    selectedMarkets.value = firstKey ? [firstKey] : [];
    await nextTick();
    setTimeout(renderChart);
  }
  catch (error) {
    console.error('Failed to fetch odds history:', error);
    message.error('获取赔率曲线失败');
  }
  finally {
    loadingHistory.value = false;
  }
};

const selectGame = (id: number) => {
  if (selectedEventId.value === id) {
    return;
  }
  selectedEventId.value = id;
  fetchHistory(id);
};

watch(marketType, () => {
  selectedEventId.value = undefined;
  history.value = null;
  selectedMarkets.value = [];
  currentPage.value = 1;
  fetchGames();
});

watch(searchValue, () => {
  currentPage.value = 1;
  clearTimeout(searchTimer.value);
  searchTimer.value = setTimeout(fetchGames, 300);
});

watch(currentPage, () => {
  selectedEventId.value = undefined;
  history.value = null;
  selectedMarkets.value = [];
  fetchGames();
});

watch(selectedMarkets, () => {
  renderChart();
}, { deep: true });

watch(isMultiSelect, (multiSelect) => {
  if (!multiSelect && selectedMarkets.value.length > 1) {
    selectedMarkets.value = [selectedMarkets.value[0]!];
  }
  renderChart();
});

onMounted(() => {
  fetchGames();
});
</script>

<template>
  <div class="odds-curve-page">
    <aside class="match-panel">
      <div class="panel-toolbar">
        <RadioGroup v-model:value="marketType" size="small">
          <Radio :value="-1">全部</Radio>
          <Radio :value="1">进行中</Radio>
          <Radio :value="2">已结束</Radio>
        </RadioGroup>
        <Button size="small" :loading="loadingGames" @click="fetchGames">刷新</Button>
      </div>
      <Input
        v-model:value="searchValue"
        allow-clear
        class="search-input"
        placeholder="搜索联赛/球队/赛事ID"
      />
      <Spin :spinning="loadingGames">
        <div class="match-list" v-if="games.length">
          <button
            v-for="game in games"
            :key="game.eventId"
            class="match-item"
            :class="{ active: game.eventId === selectedEventId }"
            type="button"
            @click="selectGame(game.eventId)"
          >
            <span class="league">{{ game.leagueName }}</span>
            <span class="teams">
              {{ game.teamHomeName }} vs {{ game.teamAwayName }}
              <span v-if="isHalfEvent(game.eventId)" class="period-tag">上半场</span>
            </span>
            <span class="meta">ID {{ game.eventId }} · {{ formatGameTime(game.startTime) }} · {{ game.marketCount }}盘</span>
          </button>
        </div>
        <Empty v-else class="list-empty" description="暂无比赛" />
      </Spin>
      <Pagination
        v-if="totalGames > pageSize"
        v-model:current="currentPage"
        class="match-pagination"
        :page-size="pageSize"
        :show-size-changer="false"
        size="small"
        :total="totalGames"
      />
    </aside>

    <main class="curve-panel">
      <div class="curve-header">
        <div>
          <div class="curve-title">{{ historyTitle }}</div>
          <div class="curve-subtitle">
            <span>赛事ID：{{ selectedEventId ?? '-' }}</span>
            <span v-if="isHalfEvent(selectedEventId)">比赛周期：上半场</span>
            <span>开赛：{{ formatTime(history?.startTime ?? selectedGame?.startTime) }}</span>
            <span>记录盘口：{{ availableMarketKeys.length }}</span>
          </div>
        </div>
      </div>

      <div class="market-selector" v-if="marketOptionGroups.length">
        <div class="market-mode">
          <span>盘口选择</span>
          <Switch
            v-model:checked="isMultiSelect"
            checked-children="多选"
            un-checked-children="单选"
          />
        </div>
        <div class="market-options">
          <div
            v-for="group in marketOptionGroups"
            :key="group.label"
            class="market-row"
          >
            <span class="market-platform">{{ group.label }}</span>
            <CheckboxGroup
              v-if="isMultiSelect"
              :options="group.options"
              :value="getGroupSelectedMarkets(group.options)"
              @change="(values) => updateGroupSelectedMarkets(group.options, values as string[])"
            />
            <RadioGroup v-else v-model:value="selectedMarket" :options="group.options" />
          </div>
        </div>
      </div>

      <Spin :spinning="loadingHistory">
        <div class="chart-wrap" :class="{ hidden: !hasChartData }">
          <EchartsUI ref="chartRef" />
        </div>
        <Empty v-if="!hasChartData" class="history-empty" description="暂无赔率历史" />
      </Spin>
    </main>
  </div>
</template>

<style scoped>
.odds-curve-page {
  display: grid;
  min-height: calc(100vh - 112px);
  grid-template-columns: 360px minmax(0, 1fr);
  gap: 12px;
  padding: 12px;
}

.match-panel,
.curve-panel {
  min-height: 0;
  border: 1px solid hsl(var(--border));
  background: hsl(var(--background));
}

.match-panel {
  display: flex;
  flex-direction: column;
}

.panel-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px;
  border-bottom: 1px solid hsl(var(--border));
}

.search-input {
  margin: 10px;
  width: calc(100% - 20px);
}

.match-list {
  height: calc(100vh - 274px);
  overflow: auto;
  padding: 0 10px 10px;
}

.match-pagination {
  display: flex;
  justify-content: center;
  padding: 10px;
  border-top: 1px solid hsl(var(--border));
}

.match-item {
  display: grid;
  width: 100%;
  gap: 4px;
  padding: 10px;
  border: 0;
  border-bottom: 1px solid hsl(var(--border));
  background: transparent;
  color: hsl(var(--foreground));
  cursor: pointer;
  text-align: left;
}

.match-item:hover,
.match-item.active {
  background: hsl(var(--accent));
}

.league,
.meta {
  color: hsl(var(--foreground) / 0.58);
  font-size: 12px;
}

.teams {
  font-size: 14px;
  font-weight: 500;
}

.period-tag {
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
  padding: 0 5px;
  border: 1px solid hsl(var(--primary) / 0.3);
  color: hsl(var(--primary));
  font-size: 12px;
  font-weight: 400;
  line-height: 18px;
}

.curve-panel {
  display: flex;
  min-width: 0;
  flex-direction: column;
}

.curve-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid hsl(var(--border));
}

.curve-title {
  font-size: 16px;
  font-weight: 600;
}

.curve-subtitle {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 6px;
  color: hsl(var(--foreground) / 0.58);
  font-size: 12px;
}

.market-selector {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 12px 16px;
  border-bottom: 1px solid hsl(var(--border));
}

.market-mode {
  display: flex;
  align-items: center;
  gap: 8px;
  color: hsl(var(--foreground) / 0.66);
  font-size: 13px;
}

.market-options {
  display: grid;
  flex: 1;
  gap: 8px;
  min-width: 0;
}

.market-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 24px;
}

.market-platform {
  width: 28px;
  flex: none;
  color: hsl(var(--foreground) / 0.58);
  font-size: 12px;
  font-weight: 600;
}

.chart-wrap {
  height: calc(100vh - 292px);
  min-height: 420px;
  padding: 12px;
}

.chart-wrap.hidden {
  height: 0;
  min-height: 0;
  overflow: hidden;
  padding: 0;
}

.history-empty,
.list-empty {
  padding-top: 80px;
}

@media (max-width: 900px) {
  .odds-curve-page {
    grid-template-columns: 1fr;
  }

  .match-list {
    height: 320px;
  }

  .chart-wrap {
    height: 420px;
  }

  .market-selector {
    align-items: flex-start;
    flex-direction: column;
  }
}
</style>
