import React, { useRef, useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2';
import ChartJS from 'chart.js/auto';
import zoomPlugin from 'chartjs-plugin-zoom';
import {
  getISOWeek,
  startOfDay,
  endOfDay,
  startOfWeek,
  endOfWeek,
  startOfMonth,
  endOfMonth,
  startOfYear,
  endOfYear,
} from 'date-fns';
import { fetchPowerTimeseries, fetchForecast } from '@/app/utils/api';
import { color } from 'html2canvas/dist/types/css/types/color';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import './datepicker-dark.css'; // custom dark mode styles
import 'chartjs-adapter-date-fns';



ChartJS.register(zoomPlugin);

interface TimeSeriesEntry {
  time: string;
  value: number;
}

interface EnergyLineChartProps {
  siteId: string;
}

function powerSeriesToEnergySeries(
  data: TimeSeriesEntry[],
  guessMinutes = 30
): TimeSeriesEntry[] {
  if (!data?.length) return [];

  // Ensure ascending by time
  const sorted = [...data].sort(
    (a, b) => new Date(a.time).getTime() - new Date(b.time).getTime()
  );

  const out: TimeSeriesEntry[] = [];
  let lastDeltaMs: number | null = null;

  for (let i = 0; i < sorted.length; i++) {
    const t0 = new Date(sorted[i].time).getTime();
    const p0 = sorted[i].value; // kW

    let deltaMs: number;
    if (i < sorted.length - 1) {
      const t1 = new Date(sorted[i + 1].time).getTime();
      deltaMs = Math.max(0, t1 - t0);
      if (deltaMs > 0) lastDeltaMs = deltaMs;
    } else {
      // For the last point, assume previous cadence or a guess
      deltaMs = lastDeltaMs ?? guessMinutes * 60 * 1000;
    }

    const hours = deltaMs / (1000 * 60 * 60);
    const kwh = p0 * hours; // kW * h = kWh

    out.push({ time: sorted[i].time, value: kwh });
  }

  return out;
}

function groupTimeSeries(
  data: TimeSeriesEntry[],
  mode: 'day' | 'daily' | 'weekly' | 'monthly' | 'yearly',
  agg: 'mean' | 'max' | 'sum' = 'mean'
): TimeSeriesEntry[] {
  const groupMap = new Map<string, number[]>();

  for (const entry of data) {
    const date = new Date(entry.time);
    let key = '';

    switch (mode) {
      case 'day': {
        // Snap to 5-minute buckets in local (KL) time
        const local = new Date(
          date.toLocaleString('en-US', { timeZone: 'Asia/Kuala_Lumpur' })
        );
        local.setSeconds(0, 0);
        key = local.toISOString();
        break;
      }
      case 'daily':
        key = date.toLocaleDateString('en-MY', {
          timeZone: 'Asia/Kuala_Lumpur',
          weekday: 'short',
          day: '2-digit',
          month: 'short',
          year: 'numeric',
        });
        break;
      case 'weekly':
        key = `${date.getFullYear()}-W${String(getISOWeek(date)).padStart(2, '0')}`;
        break;
      case 'monthly':
        key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
        break;
      case 'yearly':
        key = date.getFullYear().toString();
        break;
    }

    if (!groupMap.has(key)) groupMap.set(key, []);
    groupMap.get(key)!.push(entry.value);
  }

  return Array.from(groupMap.entries()).map(([time, values]) => {
    if (agg === 'sum') {
      const sum = values.reduce((a, b) => a + b, 0);
      return { time, value: sum };
    }
    const mean = values.reduce((a, b) => a + b, 0) / values.length;
    const max = values.reduce((a, b) => (b > a ? b : a), -Infinity);
    return { time, value: agg === 'max' ? max : mean };
  });
}

// ---- NEW: build a 5-minute time grid for the day view
function buildTimeGrid(start: Date, end: Date, stepMinutes = 5): string[] {
  const grid: string[] = [];
  const t = new Date(start);
  t.setSeconds(0, 0);
  while (t.getTime() <= end.getTime()) {
    grid.push(new Date(t).toISOString());
    t.setTime(t.getTime() + stepMinutes * 60 * 1000);
  }
  return grid;
}

const EnergyLineChart = ({ siteId }: EnergyLineChartProps) => {
  const chartRef = useRef<any>(null);
  const [viewMode, setViewMode] = useState<'day' | 'daily' | 'weekly' | 'monthly' | 'yearly'>('day');
  const [consumption, setConsumption] = useState<TimeSeriesEntry[]>([]);
  const [generation, setGeneration] = useState<TimeSeriesEntry[]>([]);
  const [selectedDate, setSelectedDate] = useState(new Date());
  const [forecast, setForecast] = useState<TimeSeriesEntry[]>([]);

  const LIVE_REFRESH_MS = 300000;       // 5min when viewing a single day
  const SLOW_REFRESH_MS = 600000;      // 10min for weekly/monthly/yearly

  const fetchAndSet = React.useCallback(async () => {
    const now = new Date();
    let start: Date;
    let end: Date;

    switch (viewMode) {
      case 'day':
        start = startOfDay(selectedDate);
        end = endOfDay(selectedDate);
        break;
      case 'daily':
        start = startOfWeek(now, { weekStartsOn: 1 });
        end = endOfWeek(now, { weekStartsOn: 1 });
        break;
      case 'weekly':
        start = startOfMonth(now);
        end = endOfMonth(now);
        break;
      case 'monthly':
        start = startOfYear(now);
        end = endOfYear(now);
        break;
      case 'yearly':
        start = new Date('2020-01-01');
        end = now;
        break;
    }

    const isoStart = start.toISOString();
    const isoEnd = end.toISOString();

    try {
      const res = await fetchPowerTimeseries(siteId, isoStart, isoEnd);
      setConsumption(res.consumption);
      setGeneration(res.generation);
    } catch (error) {
      console.error('Failed to fetch energy timeseries:', error);
    }
  }, [siteId, viewMode, selectedDate]);

  // 3) Auto-refresh effect: initial load + interval (pauses when tab hidden)
  useEffect(() => {
    let timer: number | undefined;

    const tick = async () => {
      // Avoid wasted calls when the tab is in the background
      if (!document.hidden) {
        await fetchAndSet();
      }
      const ms = viewMode === 'day' ? LIVE_REFRESH_MS : SLOW_REFRESH_MS;
      timer = window.setTimeout(tick, ms);
    };

    // initial load
    fetchAndSet();

    // schedule next cycles
    timer = window.setTimeout(tick, viewMode === 'day' ? LIVE_REFRESH_MS : SLOW_REFRESH_MS);

    const onVis = () => {
      if (!document.hidden) {
        // kick immediately when user returns
        clearTimeout(timer);
        tick();
      }
    };
    document.addEventListener('visibilitychange', onVis);

    return () => {
      clearTimeout(timer);
      document.removeEventListener('visibilitychange', onVis);
    };
  }, [fetchAndSet, viewMode]);

  function useIsDarkMode() {
    const [isDark, setIsDark] = useState(() =>
      typeof document !== 'undefined'
        ? document.body.classList.contains('dark')
        : false
    );

    useEffect(() => {
      const check = () => setIsDark(document.body.classList.contains('dark'));
      const observer = new MutationObserver(check);
      observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
      return () => observer.disconnect();
    }, []);

    return isDark;
  }

  useEffect(() => {
    const now = new Date();
    let start: Date;
    let end: Date;

    switch (viewMode) {
      case 'day':
        start = startOfDay(selectedDate);
        end = endOfDay(selectedDate);
        break;
      case 'daily':
        start = startOfWeek(now, { weekStartsOn: 1 });
        end = endOfWeek(now, { weekStartsOn: 1 });
        break;
      case 'weekly':
        start = startOfMonth(now);
        end = endOfMonth(now);
        break;
      case 'monthly':
        start = startOfYear(now);
        end = endOfYear(now);
        break;
      case 'yearly':
        start = new Date('2020-01-01');
        end = now;
        break;
    }

    const isoStart = start.toISOString();
    const isoEnd = end.toISOString();

    const fetchData = async () => {
      try {
        const res = await fetchPowerTimeseries(siteId, isoStart, isoEnd);
        setConsumption(res.consumption);
        setGeneration(res.generation);

        // ⬇️ ADD THIS here — fetch forecast
        const forecastData = await fetchForecast(3.15, 101.7, 37, 0, 30.67);
        const selectedDateStr = selectedDate.toISOString().split('T')[0];

        setForecast(
          forecastData
            .filter(({ time }) => time.startsWith(selectedDateStr))  // ✅ filter only selected date
            .map(({ time, forecast }) => ({
              time,
              value: forecast
            }))
        );

      } catch (error) {
        console.error('Failed to fetch energy timeseries:', error);
      }
    };

    fetchData();
  }, [siteId, viewMode, selectedDate]);

  const isEnergyView = viewMode !== 'day';

  // Convert to energy series for aggregated views
  const consumptionForGrouping = isEnergyView
    ? powerSeriesToEnergySeries(consumption, 30)
    : consumption;

  const generationForGrouping = isEnergyView
    ? powerSeriesToEnergySeries(generation, 30)
    : generation;

  const forecastForGrouping = isEnergyView
    ? powerSeriesToEnergySeries(forecast, 60) // if forecast is hourly, guess 60
    : forecast;

  // Group: sum for energy views, mean for day view
  const groupedConsumption = groupTimeSeries(
    consumptionForGrouping,
    viewMode,
    isEnergyView ? 'sum' : 'mean'
  );

  const groupedGeneration = groupTimeSeries(
    generationForGrouping,
    viewMode,
    isEnergyView ? 'sum' : 'mean'
  );

  const groupedForecast = groupTimeSeries(
    forecastForGrouping,
    viewMode,
    isEnergyView ? 'sum' : 'mean'
  );

  const forecastMap = Object.fromEntries(groupedForecast.map(d => [d.time, d.value]));

    const dataTimesDay = [
    ...groupedConsumption.map(d => Date.parse(d.time)),
    ...groupedGeneration.map(d => Date.parse(d.time)),
    ...groupedForecast.map(d => Date.parse(d.time)),
  ].filter(Number.isFinite).sort((a, b) => a - b);

  const dayGrid = viewMode === 'day'
  ? buildTimeGrid(startOfDay(selectedDate), endOfDay(selectedDate), 1)
  : [];
  
      
  const unionTimes = Array.from(new Set([
    ...groupedConsumption.map(d => d.time),
    ...groupedGeneration.map(d => d.time),
    ...groupedForecast.map(d => d.time),
  ])).sort((a, b) => new Date(a).getTime() - new Date(b).getTime());

  const allTimes = viewMode === 'day' ? dayGrid : unionTimes;

  const consumptionMap = Object.fromEntries(groupedConsumption.map(d => [d.time, d.value]));
  const generationMap = Object.fromEntries(groupedGeneration.map(d => [d.time, d.value]));

  const [startIndex, setStartIndex] = useState(0);
  const [endIndex, setEndIndex] = useState(allTimes.length - 1);

  // after allTimes, consumptionMap, generationMap, forecastMap
const hasDataAt = (t: string) =>
  t in consumptionMap || t in generationMap || t in forecastMap;

const firstAvailableIndex = allTimes.findIndex(hasDataAt);
const lastAvailableIndex = (() => {
  for (let i = allTimes.length - 1; i >= 0; i--) {
    if (hasDataAt(allTimes[i])) return i;
  }
  return -1;
})();

const selectableIndices =
  firstAvailableIndex === -1 || lastAvailableIndex === -1
    ? []
    : Array.from(
        { length: lastAvailableIndex - firstAvailableIndex + 1 },
        (_, k) => firstAvailableIndex + k
      );

  useEffect(() => {
  if (selectableIndices.length === 0) {
    setStartIndex(0);
    setEndIndex(Math.max(0, allTimes.length - 1));
    return;
  }
  const minIdx = selectableIndices[0];
  const maxIdx = selectableIndices[selectableIndices.length - 1];
  setStartIndex(prev => Math.min(Math.max(prev, minIdx), maxIdx));
  setEndIndex(prev => Math.min(Math.max(prev, minIdx), maxIdx));
}, [viewMode, allTimes.length, firstAvailableIndex, lastAvailableIndex]);


  useEffect(() => {
    if (typeof window !== 'undefined') {
      import('hammerjs');
    }
  }, []);

  useEffect(() => {
  if (selectableIndices.length) {
    const minIdx = selectableIndices[0];
    const maxIdx = selectableIndices[selectableIndices.length - 1];
    setStartIndex(minIdx);
    setEndIndex(maxIdx);
  } else {
    setStartIndex(0);
    setEndIndex(Math.max(0, allTimes.length - 1));
  }
  // run whenever mode changes or the timeline changes
}, [viewMode, allTimes, firstAvailableIndex, lastAvailableIndex]);


  const formatLabel = (key: string) => {
    switch (viewMode) {
      case 'day':
        return new Date(key).toLocaleTimeString('en-GB', {
          hour: '2-digit',
          minute: '2-digit',
          hour12: false,
          timeZone: 'Asia/Kuala_Lumpur',
        });
      case 'monthly':
        return new Date(`${key}-01`).toLocaleString('en-GB', { month: 'short', year: 'numeric' });
      case 'weekly':
        return key.replace('-', ' ');
      default:
        return key;
    }
  };

  const filteredLabels = allTimes.slice(startIndex, endIndex + 1);

  const minutesOfDayForLabels =
  viewMode === 'day'
    ? filteredLabels.map((iso) => {
        const d = new Date(iso);
        const kl = new Date(d.toLocaleString('en-US', { timeZone: 'Asia/Kuala_Lumpur' }));
        return kl.getHours() * 60 + kl.getMinutes();
      })
    : [];

  // ---- CHANGED: use nulls for missing buckets (not zeros)
  const filteredConsumption = filteredLabels.map(t => (t in consumptionMap ? consumptionMap[t] : null));
  const filteredGeneration  = filteredLabels.map(t => (t in generationMap  ? generationMap[t]  : null));
  const filteredForecast    = filteredLabels.map(t => (t in forecastMap    ? forecastMap[t]    : null));

  const allValues = [...filteredConsumption, ...filteredGeneration].filter(
    (v): v is number => v !== null
  );
  const maxValue = allValues.length > 0 ? Math.max(...allValues) : 0;
  const yAxisSuggestedMax = maxValue * 1.15;

  const isDark = useIsDarkMode();

  const axisColor = isDark ? '#fff' : '#222';

  function areaGradient(ctx: any, hex: string, alphaTop = 0.22, alphaBottom = 0.02) {
    const { ctx: g, chartArea } = ctx.chart;
    if (!chartArea) return hex; // initial render fallback
    const gradient = g.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
    // top more opaque → bottom fades out
    gradient.addColorStop(0, hex + Math.floor(alphaTop * 255).toString(16).padStart(2, '0'));
    gradient.addColorStop(1, hex + Math.floor(alphaBottom * 255).toString(16).padStart(2, '0'));
    return gradient;
  }

  // Define colors for both light and dark modes
  const consumptionColor = isDark ? '#B80F0A' : '#EF4444'; // Example: Brighter red for dark mode
  const generationColor = isDark ? '#48A860' : '#22C55E'; // Example: Brighter green for dark mode
  const forecastColor = '#fcd913'; // A golden yellow that works well in both modes
  const yUnit = isEnergyView ? 'kWh' : 'kW';
  const yTitle = isEnergyView ? 'Energy (kWh)' : 'Power (kW)';

  const data = {
    labels: filteredLabels.map(formatLabel),
    datasets: [
      {
        label: 'Consumption',
        data: filteredConsumption,
        borderColor: consumptionColor,
        backgroundColor: (ctx: any) => areaGradient(ctx, consumptionColor),
        fill: true,
        tension: 0.2,
        spanGaps: true,
        pointRadius: 0.7,        // default is 3, make smaller
        pointHoverRadius: 4,   // a bit bigger on hover
        borderWidth: 2,
      },
      {
        label: 'Generation',
        data: filteredGeneration,
        borderColor: generationColor,
        backgroundColor: (ctx: any) => areaGradient(ctx, generationColor),
        fill: true,
        tension: 0.2,
        spanGaps: true,
        pointRadius: 0.7,        // default is 3, make smaller
        pointHoverRadius: 4,   // a bit bigger on hover
        borderWidth: 2,
      },
      {
        label: 'Forecasted Solar',
        data: filteredForecast,
        borderColor: '#fcd913',
        backgroundColor: (ctx: any) => areaGradient(ctx, '#fcd913', 0.18, 0.03),
        tension: 0.4,
        borderDash: [5, 5],
        fill: true,
        spanGaps: true,
        pointRadius: 1,        // default is 3, make smaller
        pointHoverRadius: 4,   // a bit bigger on hover
        borderWidth: 2,
      }
    ],
  };

  const options = {
    responsive: true,
    maintainAspectRatio: false,
    normalized: true,                // faster lookup
    plugins: {
      decimation: {
      enabled: true,
      algorithm: 'lttb',           // best visual fidelity
      samples: 400,                // cap points actually drawn (~400 is a good default)
    },
      legend: {
        position: 'top',
        labels: {
          color: axisColor, // legend text color
        },
      },
      zoom: {
        zoom: {
          wheel: { enabled: true },
          pinch: { enabled: true },
          mode: 'x' as const,
        },
        pan: { enabled: true, mode: 'x' as const },
      },
      tooltip: {
        enabled: true,
        mode: 'index',
        intersect: false,
        backgroundColor: isDark ? '#232b3e' : '#fff',
        titleColor: axisColor,
        bodyColor: axisColor,
        borderColor: isDark ? '#444' : '#ccc',
        borderWidth: 1,
        callbacks: {
          label: (ctx: any) => {
            const dsLabel = ctx.dataset.label || '';
            const val = ctx.parsed.y;
            return `${dsLabel}: ${val?.toFixed(2)} ${yUnit}`;
          },
        },
      },
    },
    scales: {
      x: {
        type: 'category' as const,
        title: {
          display: true,
          color: axisColor,
          text:
            viewMode === 'day'
              ? 'Time (HH:MM)'
              : viewMode === 'daily'
              ? 'Day'
              : viewMode === 'weekly'
              ? 'Week'
              : viewMode === 'monthly'
              ? 'Month'
              : 'Year',
          font: { weight: 'normal' as const },
        },
        ticks: {
    color: axisColor,
    autoSkip: false,          // let our callback decide
    maxRotation: 0,
    callback(
        this: any,
        tickValue: string | number,
        index: number,
        ticks: any[]
      ) {
        if (viewMode !== 'day') return this.getLabelForValue(tickValue as number);

        const scale = this.chart.scales.x;
        const min = Math.max(0, Math.floor(scale.min ?? 0));
        const max = Math.min(ticks.length - 1, Math.ceil(scale.max ?? ticks.length - 1));
        const visibleCount = Math.max(1, max - min + 1);

        let step = 30;                 // ≥ 6h
        if (visibleCount < 80) step = 10;// 2–6h

        // On a category scale, tickValue is usually the index (number).
        const idx = typeof tickValue === 'number' ? tickValue : index;
        const m = minutesOfDayForLabels[idx];

        if (m != null && m % step === 0) {
          return this.getLabelForValue(idx);
        }
        return ''; // hide crowded labels
      },
  },
      },
      y: {
        beginAtZero: true,
        suggestedMax: yAxisSuggestedMax,
        title: { display: true, text: yTitle, color: axisColor, font: { weight: 'normal' as const } },
        ticks: {
          color: axisColor,
        },
      },
    },
  } as const;

  const handleResetZoom = () => {
    chartRef.current?.resetZoom();
  };

  return (
    <div className="bg-white p-4 rounded-lg shadow-md dark:bg-rtgray-800 dark:text-white-light">
      <div className="h-98 w-full"  onDoubleClick={handleResetZoom}>
        <div className="flex justify-between items-center mb-2">
          <h2 className="text-lg font-bold dark:text-white-light">Energy Consumption & Generation</h2>
          <button onClick={handleResetZoom} className="btn-primary px-8 py-2 text-sm">
            Reset
          </button>
        </div>

        <div className="mb-4 flex gap-4 items-center dark:text-white">
          {viewMode === 'day' && (
            <label className="font-medium">
              Date:{' '}
              <DatePicker
                selected={selectedDate}
                onChange={(date) => setSelectedDate(date!)}
                dateFormat="dd/MM/yyyy"  // ✅ sets correct format
                className="dark:bg-rtgray-700 dark:text-white bg-white border border-rounded dark:border-rtgray-700 text-black p-1 rounded"
              />
            </label>
          )}

          <label className="font-medium ">
            From:{' '}
            <select
              value={startIndex}
              onChange={(e) => {
                const val = Number(e.target.value);
                setStartIndex(val <= endIndex ? val : endIndex);
              }}
              disabled={selectableIndices.length === 0}
              className="dark:bg-rtgray-700 border dark:border-rtgray-700 rounded p-1"
            >
              {selectableIndices.map((absIdx) => (
                <option key={absIdx} value={absIdx}>{formatLabel(allTimes[absIdx])}</option>
              ))}
            </select>
          </label>

          <label className="font-medium ">
            To:{' '}
            <select
              value={endIndex}
              onChange={(e) => {
                const val = Number(e.target.value);
                setEndIndex(val >= startIndex ? val : startIndex);
              }}
              disabled={selectableIndices.length === 0}
              className="dark:bg-rtgray-700 border dark:border-rtgray-700 rounded p-1"
            >
              {selectableIndices.map((absIdx) => (
                <option key={absIdx} value={absIdx}>{formatLabel(allTimes[absIdx])}</option>
              ))}
            </select>
          </label>

          <label className="font-medium">
            View:{' '}
            <select
              value={viewMode}
              onChange={(e) => setViewMode(e.target.value as typeof viewMode)}
              className="dark:bg-rtgray-700 border dark:border-rtgray-700 rounded p-1"
            >
              <option value="day">Day</option>
              <option value="daily">Daily</option>
              <option value="weekly">Weekly</option>
              <option value="monthly">Monthly</option>
              <option value="yearly">Yearly</option>
            </select>
          </label>
        </div>

        <div className="h-96 w-full">
          <Line ref={chartRef} data={data} options={options} />
        </div>
      </div>
    </div>
  );
};

export default EnergyLineChart;