/**
 * TibberPrices — price curve visualization.
 *
 * - Fetches today + tomorrow price range using useEnergyPrices.
 * - For tibber kind: Recharts LineChart showing buy/sell prices over time,
 *   with the currently active price slot marked by a dot.
 * - For manual kind: shows tariff table (buy_dal, buy_normal, sell_dal, sell_normal).
 * - Handles: no active contract, empty data, loading, error.
 *
 * Recharts imports are isolated to this file only.
 */

import { useEffect, useMemo, useState } from 'react'
import {
  Stack,
  Text,
  Loader,
  Center,
  Alert,
  Table,
  Title,
  Badge,
  Group,
  Paper,
} from '@mantine/core'
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ReferenceDot,
  ResponsiveContainer,
} from 'recharts'
import { useEnergyPrices } from './hooks'
import { formatLocalDate, formatLocalTime, parseBackendTimestamp } from '../utils/datetime'

const BUY_COLOR = '#2196f3'
const SELL_COLOR = '#4caf50'

/** Slot length assumed for the very last point, when no next point bounds it. */
const FALLBACK_SLOT_MS = 60 * 60 * 1000

/** How often the "current price" marker re-evaluates which slot is active. */
const NOW_TICK_MS = 30 * 1000

// ---------------------------------------------------------------------------
// Time range helpers
// ---------------------------------------------------------------------------

function getTodayStart(): string {
  const d = new Date()
  d.setUTCHours(0, 0, 0, 0)
  return d.toISOString()
}

function getTomorrowEnd(): string {
  const d = new Date()
  d.setUTCHours(0, 0, 0, 0)
  d.setUTCDate(d.getUTCDate() + 2)
  return d.toISOString()
}

// ---------------------------------------------------------------------------
// Chart data helpers
// ---------------------------------------------------------------------------

export interface PricePoint {
  starts_at: string
  buy: number
  sell: number
  level?: string | null
}

export interface ChartRow {
  /**
   * X-axis category key — the full instant, NOT a "HH:mm" label.
   *
   * Must be unique per slot: Recharts resolves the hovered point by *value*
   * (findEntryInArray on the axis dataKey), so a repeated key makes the tooltip
   * and the active dot snap back to the first match. With "HH:mm" labels, every
   * time of day appears twice in a today+tomorrow range, which pinned the dot on
   * today once the cursor passed midnight. Formatting to HH:mm happens in the
   * tick / tooltip formatters instead.
   */
  ts: string
  /** Slot start as epoch ms; NaN when starts_at is unparseable. */
  tsMs: number
  buy: number
  sell: number
}

/** Map API price points to chart rows with unique X keys, sorted by slot start. */
export function buildChartRows(points: PricePoint[]): ChartRow[] {
  return points
    .map((p) => {
      const d = parseBackendTimestamp(p.starts_at)
      const tsMs = d.getTime()
      return {
        ts: Number.isFinite(tsMs) ? d.toISOString() : p.starts_at,
        tsMs,
        buy: p.buy,
        sell: p.sell,
      }
    })
    .sort((a, b) => {
      // Unparseable timestamps sort last so the ascending scan below can stop early.
      if (!Number.isFinite(a.tsMs)) return Number.isFinite(b.tsMs) ? 1 : 0
      if (!Number.isFinite(b.tsMs)) return -1
      return a.tsMs - b.tsMs
    })
}

/**
 * Index of the row whose slot contains `nowMs`, or null when now is outside the
 * fetched range. A slot ends where the next one starts; the last row has no next
 * slot, so it falls back to the series spacing (quarter-hourly for Tibber).
 */
export function findActiveSlotIndex(rows: ChartRow[], nowMs: number): number | null {
  let idx = -1
  for (let i = 0; i < rows.length; i += 1) {
    if (!Number.isFinite(rows[i].tsMs) || rows[i].tsMs > nowMs) break
    idx = i
  }
  if (idx < 0) return null

  const spacing = rows.length > 1 ? rows[1].tsMs - rows[0].tsMs : NaN
  const slotMs = Number.isFinite(spacing) && spacing > 0 ? spacing : FALLBACK_SLOT_MS
  const slotEnd = idx + 1 < rows.length ? rows[idx + 1].tsMs : rows[idx].tsMs + slotMs
  return nowMs < slotEnd ? idx : null
}

/** Ticking clock so the active-slot marker follows slot boundaries while open. */
function useNowMs(intervalMs = NOW_TICK_MS): number {
  const [now, setNow] = useState(() => Date.now())
  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), intervalMs)
    return () => clearInterval(id)
  }, [intervalMs])
  return now
}

// ---------------------------------------------------------------------------
// Tibber chart
// ---------------------------------------------------------------------------

interface TibberChartProps {
  points: PricePoint[]
  currency: string
}

function TibberChart({ points, currency }: TibberChartProps) {
  const data = useMemo(() => buildChartRows(points), [points])
  const nowMs = useNowMs()
  const activeIndex = findActiveSlotIndex(data, nowMs)
  const activeRow = activeIndex == null ? null : data[activeIndex]

  return (
    <Stack gap="xs" data-testid="tibber-chart">
      <Group gap="xs" justify="space-between" align="baseline">
        <Title order={6} c="dimmed">
          Price curve ({currency})
        </Title>
        {activeRow && (
          <Text size="xs" c="dimmed" data-testid="tibber-current-price">
            Now {formatLocalTime(activeRow.ts)} · buy {activeRow.buy.toFixed(4)} · sell{' '}
            {activeRow.sell.toFixed(4)}
          </Text>
        )}
      </Group>
      <ResponsiveContainer width="100%" height={260}>
        <LineChart data={data} margin={{ top: 4, right: 16, left: 0, bottom: 4 }}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis
            dataKey="ts"
            tick={{ fontSize: 10 }}
            interval="preserveStartEnd"
            tickFormatter={(v: string) => formatLocalTime(v)}
          />
          <YAxis
            tick={{ fontSize: 10 }}
            tickFormatter={(v: number) => v.toFixed(3)}
          />
          <Tooltip
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            formatter={(val: any) =>
              [`${typeof val === 'number' ? val.toFixed(4) : String(val)} ${currency}`, undefined]
            }
            labelFormatter={(label) =>
              typeof label === 'string'
                ? `${formatLocalDate(label)} ${formatLocalTime(label)}`
                : label
            }
          />
          <Legend />
          <Line
            type="monotone"
            dataKey="buy"
            stroke={BUY_COLOR}
            dot={false}
            strokeWidth={2}
            name="Buy"
          />
          <Line
            type="monotone"
            dataKey="sell"
            stroke={SELL_COLOR}
            dot={false}
            strokeWidth={2}
            name="Sell"
          />
          {/* Currently active price slot, marked by default (no hover needed). */}
          {activeRow && (
            <ReferenceDot
              x={activeRow.ts}
              y={activeRow.buy}
              r={4}
              fill={BUY_COLOR}
              stroke="#fff"
              strokeWidth={2}
            />
          )}
          {activeRow && (
            <ReferenceDot
              x={activeRow.ts}
              y={activeRow.sell}
              r={4}
              fill={SELL_COLOR}
              stroke="#fff"
              strokeWidth={2}
            />
          )}
        </LineChart>
      </ResponsiveContainer>
    </Stack>
  )
}

// ---------------------------------------------------------------------------
// Manual tariff table
// ---------------------------------------------------------------------------

interface ManualTariffTableProps {
  tariff: {
    buy_dal: number
    buy_normal: number
    sell_dal: number
    sell_normal: number
  }
  currency: string
}

function ManualTariffTable({ tariff, currency }: ManualTariffTableProps) {
  return (
    <Stack gap="xs" data-testid="manual-tariff-table">
      <Title order={6} c="dimmed">
        Fixed tariff ({currency}/kWh)
      </Title>
      <Table withTableBorder withColumnBorders>
        <Table.Thead>
          <Table.Tr>
            <Table.Th>Tariff</Table.Th>
            <Table.Th>Buy</Table.Th>
            <Table.Th>Sell</Table.Th>
          </Table.Tr>
        </Table.Thead>
        <Table.Tbody>
          <Table.Tr>
            <Table.Td>Normal (peak)</Table.Td>
            <Table.Td data-testid="tariff-buy-normal">{tariff.buy_normal.toFixed(4)}</Table.Td>
            <Table.Td data-testid="tariff-sell-normal">{tariff.sell_normal.toFixed(4)}</Table.Td>
          </Table.Tr>
          <Table.Tr>
            <Table.Td>Dal (off-peak)</Table.Td>
            <Table.Td data-testid="tariff-buy-dal">{tariff.buy_dal.toFixed(4)}</Table.Td>
            <Table.Td data-testid="tariff-sell-dal">{tariff.sell_dal.toFixed(4)}</Table.Td>
          </Table.Tr>
        </Table.Tbody>
      </Table>
    </Stack>
  )
}

// ---------------------------------------------------------------------------
// TibberPrices — main component
// ---------------------------------------------------------------------------

export function TibberPrices() {
  const start = getTodayStart()
  const end = getTomorrowEnd()

  const { data, isLoading, isError } = useEnergyPrices(start, end)

  if (isLoading) {
    return (
      <Center py="xl" data-testid="prices-loading">
        <Loader />
      </Center>
    )
  }

  if (isError) {
    return (
      <Alert color="red" data-testid="prices-error">
        Failed to load energy prices. Please refresh.
      </Alert>
    )
  }

  if (!data) {
    return (
      <Alert color="gray" data-testid="prices-no-data">
        No pricing data available.
      </Alert>
    )
  }

  // No active contract
  if (!data.kind) {
    return (
      <Paper withBorder p="md" data-testid="prices-no-contract">
        <Stack gap="xs">
          <Text fw={500}>No active contract</Text>
          <Text size="sm" c="dimmed">
            Activate an energy contract on the Contracts tab to see pricing data.
          </Text>
        </Stack>
      </Paper>
    )
  }

  const currency = data.currency

  return (
    <Stack gap="lg" data-testid="tibber-prices">
      <Group gap="sm" align="center">
        <Text fw={500}>Energy Prices</Text>
        <Badge variant="outline" size="sm">
          {data.kind}
        </Badge>
        <Text size="xs" c="dimmed">
          {currency}
        </Text>
      </Group>

      {data.kind === 'tibber' && data.points && data.points.length > 0 && (
        <TibberChart points={data.points} currency={currency} />
      )}

      {data.kind === 'tibber' && (!data.points || data.points.length === 0) && (
        <Alert color="yellow" data-testid="tibber-no-prices">
          No Tibber price points available for the selected time range. Check Tibber configuration.
        </Alert>
      )}

      {data.kind === 'manual' && data.tariff && (
        <ManualTariffTable tariff={data.tariff} currency={currency} />
      )}

      {data.kind === 'manual' && !data.tariff && (
        <Alert color="yellow" data-testid="manual-no-tariff">
          Manual tariff data not available.
        </Alert>
      )}
    </Stack>
  )
}
