export type LiveShowModerationMode = 'off' | 'manual' | 'trusted_only';
export type LiveShowPlaybackMode = 'newest_first' | 'balanced' | 'curated';
export type LiveShowPaceMode = 'auto' | 'fixed';
export type LiveShowLayoutMode = 'single' | 'split' | 'grid_burst';
export type LiveShowEffectPreset =
  | 'film_cut'
  | 'shutter_flash'
  | 'polaroid_toss'
  | 'parallax_glide'
  | 'light_effects';
export type LiveShowBackgroundMode = 'blur_last' | 'gradient' | 'solid' | 'brand';

export type LiveShowSettings = {
  retention_window_hours: number;
  moderation_mode: LiveShowModerationMode;
  playback_mode: LiveShowPlaybackMode;
  pace_mode: LiveShowPaceMode;
  fixed_interval_seconds: number;
  layout_mode: LiveShowLayoutMode;
  effect_preset: LiveShowEffectPreset;
  effect_intensity: number;
  background_mode: LiveShowBackgroundMode;
};

export type LiveShowCursor = {
  approved_at: string | null;
  id: number;
};

export type LiveShowEvent = {
  id: number;
  slug?: string | null;
  name: string;
  default_locale?: string | null;
};

export type LiveShowPhoto = {
  id: number;
  full_url: string;
  thumb_url: string | null;
  approved_at: string | null;
  width?: number | null;
  height?: number | null;
  is_featured: boolean;
  live_priority: number;
};

export type LiveShowState = {
  event: LiveShowEvent;
  settings: LiveShowSettings;
  settings_version: string;
  photos: LiveShowPhoto[];
  cursor: LiveShowCursor | null;
};

export type LiveShowUpdates = {
  settings: LiveShowSettings | null;
  settings_version: string;
  photos: LiveShowPhoto[];
  cursor: LiveShowCursor | null;
};

export type LiveShowErrorCode = 'not_found' | 'invalid_cursor' | 'rate_limited' | 'unknown';

export class LiveShowError extends Error {
  readonly code: LiveShowErrorCode;
  readonly status?: number;

  constructor(code: LiveShowErrorCode, message: string, status?: number) {
    super(message);
    this.name = 'LiveShowError';
    this.code = code;
    this.status = status;
  }
}

export const DEFAULT_LIVE_SHOW_SETTINGS: LiveShowSettings = {
  retention_window_hours: 12,
  moderation_mode: 'manual',
  playback_mode: 'newest_first',
  pace_mode: 'auto',
  fixed_interval_seconds: 8,
  layout_mode: 'single',
  effect_preset: 'film_cut',
  effect_intensity: 70,
  background_mode: 'blur_last',
};

const DEFAULT_EVENT_NAME = 'Fotospiel Live Show';

function coerceLocalized(value: unknown, fallback: string): string {
  if (typeof value === 'string' && value.trim() !== '') {
    return value;
  }

  if (value && typeof value === 'object') {
    const obj = value as Record<string, unknown>;
    const preferredKeys = ['de', 'en'];

    for (const key of preferredKeys) {
      const candidate = obj[key];
      if (typeof candidate === 'string' && candidate.trim() !== '') {
        return candidate;
      }
    }

    const firstString = Object.values(obj).find((candidate) => typeof candidate === 'string' && candidate.trim() !== '');
    if (typeof firstString === 'string') {
      return firstString;
    }
  }

  return fallback;
}

function toNumber(value: unknown, fallback: number): number {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : fallback;
}

export function normalizeLiveShowSettings(raw?: Partial<LiveShowSettings> | null): LiveShowSettings {
  const merged = {
    ...DEFAULT_LIVE_SHOW_SETTINGS,
    ...(raw ?? {}),
  };

  return {
    retention_window_hours: toNumber(merged.retention_window_hours, DEFAULT_LIVE_SHOW_SETTINGS.retention_window_hours),
    moderation_mode: merged.moderation_mode,
    playback_mode: merged.playback_mode,
    pace_mode: merged.pace_mode,
    fixed_interval_seconds: toNumber(merged.fixed_interval_seconds, DEFAULT_LIVE_SHOW_SETTINGS.fixed_interval_seconds),
    layout_mode: merged.layout_mode,
    effect_preset: merged.effect_preset,
    effect_intensity: toNumber(merged.effect_intensity, DEFAULT_LIVE_SHOW_SETTINGS.effect_intensity),
    background_mode: merged.background_mode,
  };
}

function normalizeLiveShowEvent(raw: Record<string, unknown>): LiveShowEvent {
  return {
    id: Number(raw.id ?? 0),
    slug: typeof raw.slug === 'string' ? raw.slug : null,
    name: coerceLocalized(raw.name, DEFAULT_EVENT_NAME),
    default_locale: typeof raw.default_locale === 'string' ? raw.default_locale : null,
  };
}

function normalizeLiveShowPhoto(raw: Record<string, unknown>): LiveShowPhoto {
  return {
    id: Number(raw.id ?? 0),
    full_url: String(raw.full_url ?? ''),
    thumb_url: typeof raw.thumb_url === 'string' ? raw.thumb_url : null,
    approved_at: typeof raw.approved_at === 'string' ? raw.approved_at : null,
    width: typeof raw.width === 'number' ? raw.width : null,
    height: typeof raw.height === 'number' ? raw.height : null,
    is_featured: Boolean(raw.is_featured),
    live_priority: Number(raw.live_priority ?? 0),
  };
}

function normalizeCursor(raw: Record<string, unknown> | null): LiveShowCursor | null {
  if (!raw) {
    return null;
  }

  return {
    approved_at: typeof raw.approved_at === 'string' ? raw.approved_at : null,
    id: Number(raw.id ?? 0),
  };
}

function resolveErrorCode(status: number, payload: Record<string, unknown> | null): LiveShowErrorCode {
  const error = typeof payload?.error === 'string' ? payload.error : null;

  if (error === 'live_show_not_found' || status === 404) {
    return 'not_found';
  }

  if (error === 'invalid_cursor' || status === 422) {
    return 'invalid_cursor';
  }

  if (status === 429) {
    return 'rate_limited';
  }

  return 'unknown';
}

async function handleResponse<T>(response: Response): Promise<T> {
  if (response.status === 204) {
    return {} as T;
  }

  const data = await response.json().catch(() => null);

  if (!response.ok) {
    const payload = data as Record<string, unknown> | null;
    const code = resolveErrorCode(response.status, payload);
    const message =
      typeof payload?.message === 'string'
        ? payload.message
        : typeof payload?.error === 'string'
          ? payload.error
          : 'Live show request failed';
    throw new LiveShowError(code, message, response.status);
  }

  return data as T;
}

function buildParams(options: {
  cursor?: LiveShowCursor | null;
  settingsVersion?: string;
  limit?: number;
} = {}): URLSearchParams {
  const params = new URLSearchParams();
  if (options.limit) {
    params.set('limit', String(options.limit));
  }
  if (options.settingsVersion) {
    params.set('settings_version', options.settingsVersion);
  }
  if (options.cursor?.approved_at) {
    params.set('after_approved_at', options.cursor.approved_at);
    params.set('after_id', String(options.cursor.id));
  }
  return params;
}

export function buildLiveShowStreamUrl(
  token: string,
  options: {
    cursor?: LiveShowCursor | null;
    settingsVersion?: string;
    limit?: number;
  } = {}
): string {
  const params = buildParams(options);
  const base = `/api/v1/live-show/${encodeURIComponent(token)}/stream`;
  const query = params.toString();
  return query ? `${base}?${query}` : base;
}

export async function fetchLiveShowState(token: string, limit = 50): Promise<LiveShowState> {
  const params = new URLSearchParams();
  if (limit) {
    params.set('limit', String(limit));
  }

  const response = await fetch(
    `/api/v1/live-show/${encodeURIComponent(token)}${params.toString() ? `?${params.toString()}` : ''}`,
    {
      headers: { Accept: 'application/json' },
      credentials: 'omit',
    }
  );

  const data = await handleResponse<Record<string, unknown>>(response);
  const rawEvent = (data.event as Record<string, unknown>) ?? {};

  return {
    event: normalizeLiveShowEvent(rawEvent),
    settings: normalizeLiveShowSettings(data.settings as Partial<LiveShowSettings> | null),
    settings_version: String(data.settings_version ?? ''),
    photos: Array.isArray(data.photos)
      ? data.photos.map((photo) => normalizeLiveShowPhoto(photo as Record<string, unknown>))
      : [],
    cursor: normalizeCursor((data.cursor as Record<string, unknown>) ?? null),
  };
}

export async function fetchLiveShowUpdates(
  token: string,
  options: {
    cursor?: LiveShowCursor | null;
    settingsVersion?: string;
    limit?: number;
  } = {}
): Promise<LiveShowUpdates> {
  const params = buildParams(options);

  const response = await fetch(
    `/api/v1/live-show/${encodeURIComponent(token)}/updates${params.toString() ? `?${params.toString()}` : ''}`,
    {
      headers: { Accept: 'application/json' },
      credentials: 'omit',
    }
  );

  const data = await handleResponse<Record<string, unknown>>(response);

  return {
    settings: data.settings ? normalizeLiveShowSettings(data.settings as Partial<LiveShowSettings>) : null,
    settings_version: String(data.settings_version ?? ''),
    photos: Array.isArray(data.photos)
      ? data.photos.map((photo) => normalizeLiveShowPhoto(photo as Record<string, unknown>))
      : [],
    cursor: normalizeCursor((data.cursor as Record<string, unknown>) ?? null),
  };
}
