import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useReducer,
  type ReactNode,
} from 'react';

import { api } from './api';
import { registerForPushNotificationsAsync } from './notifications';
import type {
  AppState,
  ChaosLevel,
  Game,
  Match,
  Player,
  PlayerId,
  ScoringMode,
} from './types';

export type StoreStatus = 'loading' | 'ready' | 'offline';

export function decideWinner(
  scoring: ScoringMode,
  scores: Record<PlayerId, number>,
): PlayerId | null {
  if (scores.p1 === scores.p2) return null;
  const p1Better = scoring === 'lowWins' ? scores.p1 < scores.p2 : scores.p1 > scores.p2;
  return p1Better ? 'p1' : 'p2';
}

const emptyState: AppState = {
  players: {
    p1: { id: 'p1', name: 'Player 1', alias: '' },
    p2: { id: 'p2', name: 'Player 2', alias: '' },
  },
  games: [],
  matches: [],
  settings: { chaos: 'medium', currentPlayerId: 'p1' },
};

type InternalAction =
  | { type: 'hydrate'; players: Player[]; games: Game[]; matches: Match[]; chaos: ChaosLevel }
  | { type: 'upsertMatch'; match: Match }
  | { type: 'addGame'; game: Game }
  | { type: 'patchPlayer'; playerId: PlayerId; patch: Partial<Pick<Player, 'name' | 'alias'>> }
  | { type: 'setChaos'; chaos: ChaosLevel }
  | { type: 'setCurrentPlayer'; playerId: PlayerId };

function reducer(state: AppState, action: InternalAction): AppState {
  switch (action.type) {
    case 'hydrate': {
      const players = { ...state.players };
      for (const player of action.players) players[player.id] = player;
      return {
        players,
        games: action.games,
        matches: action.matches,
        settings: { ...state.settings, chaos: action.chaos },
      };
    }
    case 'upsertMatch': {
      const exists = state.matches.some((m) => m.id === action.match.id);
      return {
        ...state,
        matches: exists
          ? state.matches.map((m) => (m.id === action.match.id ? action.match : m))
          : [action.match, ...state.matches],
      };
    }
    case 'addGame':
      return { ...state, games: [...state.games, action.game] };
    case 'patchPlayer': {
      const player = { ...state.players[action.playerId], ...action.patch };
      return { ...state, players: { ...state.players, [action.playerId]: player } };
    }
    case 'setChaos':
      return { ...state, settings: { ...state.settings, chaos: action.chaos } };
    case 'setCurrentPlayer':
      return { ...state, settings: { ...state.settings, currentPlayerId: action.playerId } };
  }
}

interface StoreValue {
  state: AppState;
  status: StoreStatus;
  refresh: () => Promise<void>;
  actions: {
    logMatch: (gameId: string, scores: Record<PlayerId, number>, note: string) => Promise<Match>;
    addGame: (name: string, emoji: string, scoring: ScoringMode) => Promise<void>;
    /** Update locally as the user types; persist with savePlayer on blur. */
    patchPlayerLocal: (
      playerId: PlayerId,
      patch: Partial<Pick<Player, 'name' | 'alias'>>,
    ) => void;
    savePlayer: (playerId: PlayerId) => Promise<void>;
    fileDispute: (matchId: string, statement: string) => Promise<void>;
    resolveDispute: (matchId: string, status: 'score-stands' | 'overturned') => Promise<void>;
    setChaos: (chaos: ChaosLevel) => Promise<void>;
    setCurrentPlayer: (playerId: PlayerId) => void;
  };
}

const StoreContext = createContext<StoreValue | null>(null);

export function StoreProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(reducer, emptyState);
  const [status, setStatus] = useReducer(
    (_prev: StoreStatus, next: StoreStatus) => next,
    'loading',
  );

  const refresh = useCallback(async () => {
    try {
      const [players, games, matches, settings] = await Promise.all([
        api.players(),
        api.games(),
        api.matches(),
        api.settings(),
      ]);
      dispatch({ type: 'hydrate', players, games, matches, chaos: settings.chaos ?? 'medium' });
      setStatus('ready');
    } catch {
      setStatus('offline');
    }
  }, []);

  useEffect(() => {
    refresh();
  }, [refresh]);

  // Register this phone's push token under whichever player owns the phone.
  const currentPlayerId = state.settings.currentPlayerId;
  useEffect(() => {
    if (status !== 'ready') return;
    registerForPushNotificationsAsync().then((token) => {
      if (token) api.registerPushToken(currentPlayerId, token).catch(() => {});
    });
  }, [status, currentPlayerId]);

  const value = useMemo<StoreValue>(
    () => ({
      state,
      status,
      refresh,
      actions: {
        async logMatch(gameId, scores, note) {
          const match = await api.logMatch({
            gameId,
            p1Score: scores.p1,
            p2Score: scores.p2,
            note: note.trim() || undefined,
            loggedBy: state.settings.currentPlayerId,
          });
          dispatch({ type: 'upsertMatch', match });
          return match;
        },
        async addGame(name, emoji, scoring) {
          const game = await api.addGame({ name, emoji, scoring });
          dispatch({ type: 'addGame', game });
        },
        patchPlayerLocal(playerId, patch) {
          dispatch({ type: 'patchPlayer', playerId, patch });
        },
        async savePlayer(playerId) {
          const { name, alias } = state.players[playerId];
          await api.updatePlayer(playerId, { name, alias });
        },
        async fileDispute(matchId, statement) {
          await api.fileDispute(matchId, statement, state.settings.currentPlayerId);
          const match = state.matches.find((m) => m.id === matchId);
          if (match) {
            dispatch({
              type: 'upsertMatch',
              match: {
                ...match,
                dispute: {
                  statement: statement.trim(),
                  filedBy: state.settings.currentPlayerId,
                  status: 'open',
                  filedAt: new Date().toISOString(),
                },
              },
            });
          }
        },
        async resolveDispute(matchId, resolveStatus) {
          const match = await api.resolveDispute(matchId, resolveStatus);
          dispatch({ type: 'upsertMatch', match });
        },
        async setChaos(chaos) {
          dispatch({ type: 'setChaos', chaos });
          await api.setChaos(chaos);
        },
        setCurrentPlayer(playerId) {
          dispatch({ type: 'setCurrentPlayer', playerId });
        },
      },
    }),
    [state, status, refresh],
  );

  return <StoreContext.Provider value={value}>{children}</StoreContext.Provider>;
}

export function useStore() {
  const ctx = useContext(StoreContext);
  if (!ctx) throw new Error('useStore must be used inside StoreProvider');
  return ctx;
}

// ---------- derived stats ----------

export interface Record2P {
  p1: number;
  p2: number;
  ties: number;
}

export function gameRecord(state: AppState, gameId: string): Record2P {
  const record: Record2P = { p1: 0, p2: 0, ties: 0 };
  for (const match of state.matches) {
    if (match.gameId !== gameId) continue;
    if (match.winnerId) record[match.winnerId] += 1;
    else record.ties += 1;
  }
  return record;
}

export function overallTotals(state: AppState) {
  const wins: Record2P = { p1: 0, p2: 0, ties: 0 };
  const points: Record<PlayerId, number> = { p1: 0, p2: 0 };
  for (const match of state.matches) {
    if (match.winnerId) wins[match.winnerId] += 1;
    else wins.ties += 1;
    points.p1 += match.scores.p1;
    points.p2 += match.scores.p2;
  }
  const leaderId: PlayerId | null = wins.p1 === wins.p2 ? null : wins.p1 > wins.p2 ? 'p1' : 'p2';
  return { wins, points, leaderId };
}

/** Current win streak within one game (or across all games when gameId is undefined). */
export function currentStreak(state: AppState, gameId?: string) {
  let holder: PlayerId | null = null;
  let length = 0;
  for (const match of state.matches) {
    if (gameId && match.gameId !== gameId) continue;
    if (!match.winnerId) break;
    if (holder === null) {
      holder = match.winnerId;
      length = 1;
    } else if (match.winnerId === holder) {
      length += 1;
    } else {
      break;
    }
  }
  return holder ? { holder, length } : null;
}

export function matchesForGame(state: AppState, gameId: string): Match[] {
  return state.matches.filter((match) => match.gameId === gameId);
}

export function lastPlayed(state: AppState, gameId: string): string | null {
  return matchesForGame(state, gameId)[0]?.playedAt ?? null;
}
