import { useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';

import { fetchDailyCounts, fetchHourlyAvgs, fetchSources } from '../api/client';

export function ContributionStats() {
  const sourcesQ = useQuery({
    queryKey: ['sources'],
    queryFn: fetchSources,
    staleTime: 60_000,
  });

  const earliest = useMemo(() => {
    if (!sourcesQ.data) return null;
    const dates = sourcesQ.data
      .map((s) => s.earliest)
      .filter((d): d is string => d != null)
      .map((d) => new Date(d));
    return dates.length > 0 ? new Date(Math.min(...dates.map((d) => d.getTime()))) : null;
  }, [sourcesQ.data]);

  const to = new Date();
  const from = earliest ?? new Date(to.getFullYear() - 5, 0, 1);
  const fromStr = fmt(from);
  const toStr = fmt(to);

  const dailyQ = useQuery({
    queryKey: ['daily-counts-alltime', fromStr, toStr],
    queryFn: () => fetchDailyCounts(fromStr, toStr),
    enabled: !!earliest,
    staleTime: 10 * 60_000,
  });

  // Bucket hour-of-day in the user's local timezone so the chart matches
  // the clock they see. Browser may report e.g. "Europe/Helsinki"; fall
  // back to UTC if the resolver returns something the server won't
  // accept (it validates the string before binding).
  const tz = useMemo(
    () => Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
    [],
  );
  const hourlyQ = useQuery({
    queryKey: ['hourly-avgs-alltime', fromStr, toStr, tz],
    queryFn: () => fetchHourlyAvgs(fromStr, toStr, tz),
    enabled: !!earliest,
    staleTime: 10 * 60_000,
  });

  const stats = useMemo(() => {
    const counts = dailyQ.data ?? [];
    if (counts.length === 0) return null;

    // Build a set of dates with contributions
    const countMap = new Map(counts.map((d) => [d.date, d.count]));

    // Current streak (consecutive days ending today or yesterday with contributions)
    let currentStreak = 0;
    const cursor = new Date(to);
    // Allow today to have 0 (day isn't over yet) — start from yesterday if today is 0
    if ((countMap.get(fmt(cursor)) ?? 0) > 0) {
      currentStreak = 1;
      cursor.setDate(cursor.getDate() - 1);
    } else {
      cursor.setDate(cursor.getDate() - 1);
      if ((countMap.get(fmt(cursor)) ?? 0) > 0) {
        currentStreak = 1;
        cursor.setDate(cursor.getDate() - 1);
      }
    }
    if (currentStreak > 0) {
      while ((countMap.get(fmt(cursor)) ?? 0) > 0) {
        currentStreak++;
        cursor.setDate(cursor.getDate() - 1);
      }
    }

    // Longest streak
    let longestStreak = 0;
    let streak = 0;
    const sorted = [...counts].sort((a, b) => a.date.localeCompare(b.date));
    for (let i = 0; i < sorted.length; i++) {
      if (sorted[i].count > 0) {
        streak++;
        if (streak > longestStreak) longestStreak = streak;
      } else {
        streak = 0;
      }
    }

    // Busiest day
    const busiest = sorted.reduce((best, d) => (d.count > best.count ? d : best), sorted[0]);

    // Day-of-week averages
    const dayTotals = [0, 0, 0, 0, 0, 0, 0];
    const dayCounts = [0, 0, 0, 0, 0, 0, 0];
    for (const d of sorted) {
      const dow = new Date(d.date + 'T00:00:00').getDay();
      dayTotals[dow] += d.count;
      dayCounts[dow]++;
    }
    const dayNames = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
    const dayAvgs = dayNames.map((name, i) => ({
      name,
      avg: dayCounts[i] > 0 ? dayTotals[i] / dayCounts[i] : 0,
    }));
    const maxAvg = Math.max(...dayAvgs.map((d) => d.avg));

    // Total active days
    const activeDays = sorted.filter((d) => d.count > 0).length;

    return { currentStreak, longestStreak, busiest, dayAvgs, maxAvg, activeDays };
  }, [dailyQ.data]);

  const hourly = useMemo(() => {
    const data = hourlyQ.data ?? [];
    if (data.length === 0) return null;
    const byHour = new Array(24).fill(0);
    for (const { hour, avg } of data) {
      if (hour >= 0 && hour < 24) byHour[hour] = avg;
    }
    const max = Math.max(...byHour);
    return { hours: byHour, max };
  }, [hourlyQ.data]);

  if (!stats) return null;

  return (
    <div>
      <p style={{ fontSize: '0.8rem', opacity: 0.6 }}>contribution stats</p>
      <div className="d-flex flex-column gap-2" style={{ fontSize: '0.8rem' }}>
        <div className="d-flex justify-content-between">
          <span style={{ opacity: 0.7 }}>current streak</span>
          <span>{stats.currentStreak} {stats.currentStreak === 1 ? 'day' : 'days'}</span>
        </div>
        <div className="d-flex justify-content-between">
          <span style={{ opacity: 0.7 }}>longest streak</span>
          <span>{stats.longestStreak} {stats.longestStreak === 1 ? 'day' : 'days'}</span>
        </div>
        <div className="d-flex justify-content-between">
          <span style={{ opacity: 0.7 }}>busiest day</span>
          <span>{stats.busiest.count} on {stats.busiest.date}</span>
        </div>
        <div className="d-flex justify-content-between">
          <span style={{ opacity: 0.7 }}>active days</span>
          <span>{stats.activeDays.toLocaleString()}</span>
        </div>
        <div className="mt-1">
          <span style={{ opacity: 0.7, fontSize: '0.75rem' }}>avg by weekday</span>
          <div className="d-flex align-items-end gap-1 mt-1" style={{ height: 64 }}>
            {stats.dayAvgs.map(({ name, avg }) => (
              <div key={name} className="d-flex flex-column align-items-center" style={{ flex: 1, height: '100%', justifyContent: 'flex-end' }}>
                <span style={{ fontSize: '0.65rem', opacity: 0.6, marginBottom: 2 }}>{avg.toFixed(1)}</span>
                <div style={{ width: '100%', maxWidth: 20, borderRadius: 3, background: 'rgba(255,255,255,0.05)', flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
                  <div
                    style={{
                      height: stats.maxAvg > 0 ? `${(avg / stats.maxAvg) * 100}%` : '0%',
                      borderRadius: 3,
                      backgroundColor: '#39d353',
                      opacity: 0.7,
                    }}
                  />
                </div>
                <span style={{ fontSize: '0.65rem', opacity: 0.7, marginTop: 2 }}>{name}</span>
              </div>
            ))}
          </div>
        </div>
        {hourly && (
          <div className="mt-3">
            <span style={{ opacity: 0.7, fontSize: '0.75rem' }}>avg by hour ({tz})</span>
            <div className="d-flex align-items-end gap-1 mt-1" style={{ height: 64 }}>
              {hourly.hours.map((avg, h) => (
                <div key={h} className="d-flex flex-column align-items-center" style={{ flex: 1, height: '100%', justifyContent: 'flex-end' }}>
                  <div style={{ width: '100%', borderRadius: 2, background: 'rgba(255,255,255,0.05)', flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
                    <div
                      style={{
                        height: hourly.max > 0 ? `${(avg / hourly.max) * 100}%` : '0%',
                        borderRadius: 2,
                        backgroundColor: '#39d353',
                        opacity: 0.7,
                      }}
                      title={`${h.toString().padStart(2, '0')}:00 — ${avg.toFixed(2)}/day`}
                    />
                  </div>
                  <span style={{ fontSize: '0.6rem', opacity: 0.7, marginTop: 2, minHeight: '0.7rem' }}>
                    {h % 4 === 0 ? h.toString().padStart(2, '0') : ''}
                  </span>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

function fmt(d: Date): string {
  return d.toISOString().slice(0, 10);
}
