import { getDeviceId } from '../lib/device';
import { DEFAULT_LOCALE } from '../i18n/messages';

export interface EventBrandingPayload {
  primary_color?: string | null;
  secondary_color?: string | null;
  background_color?: string | null;
  font_family?: string | null;
  logo_url?: string | null;
  surface_color?: string | null;
  heading_font?: string | null;
  body_font?: string | null;
  font_size?: 's' | 'm' | 'l' | null;
  icon?: string | null;
  logo_mode?: 'emoticon' | 'upload' | null;
  logo_value?: string | null;
  logo_position?: 'left' | 'right' | 'center' | null;
  logo_size?: 's' | 'm' | 'l' | null;
  button_style?: 'filled' | 'outline' | null;
  button_radius?: number | null;
  button_primary_color?: string | null;
  button_secondary_color?: string | null;
  link_color?: string | null;
  mode?: 'light' | 'dark' | 'auto' | null;
  use_default_branding?: boolean | null;
  palette?: {
    primary?: string | null;
    secondary?: string | null;
    background?: string | null;
    surface?: string | null;
  } | null;
  typography?: {
    heading?: string | null;
    body?: string | null;
    size?: 's' | 'm' | 'l' | null;
  } | null;
  logo?: {
    mode?: 'emoticon' | 'upload';
    value?: string | null;
    position?: 'left' | 'right' | 'center';
    size?: 's' | 'm' | 'l';
  } | null;
  buttons?: {
    style?: 'filled' | 'outline';
    radius?: number | null;
    primary?: string | null;
    secondary?: string | null;
    link_color?: string | null;
  } | null;
}

export interface EventData {
  id: number;
  slug: string;
  name: string;
  default_locale: string;
  engagement_mode?: 'tasks' | 'photo_only';
  created_at: string;
  updated_at: string;
  join_token?: string | null;
  demo_read_only?: boolean;
  photobooth_enabled?: boolean | null;
  type?: {
    slug: string;
    name: string;
    icon: string | null;
  };
  branding?: EventBrandingPayload | null;
  guest_upload_visibility?: 'immediate' | 'review';
  live_show?: {
    moderation_mode?: 'off' | 'manual' | 'trusted_only';
  };
}

export interface PackageData {
  id: number;
  name: string;
  max_photos: number;
  max_guests?: number | null;
  gallery_days?: number | null;
}

export interface LimitUsageSummary {
  limit: number | null;
  used: number;
  remaining: number | null;
  percentage: number | null;
  state: 'ok' | 'warning' | 'limit_reached' | 'unlimited';
  threshold_reached: number | null;
  next_threshold: number | null;
  thresholds: number[];
}

export interface GallerySummary {
  state: 'ok' | 'warning' | 'expired' | 'unlimited';
  expires_at: string | null;
  days_remaining: number | null;
  warning_thresholds: number[];
  warning_triggered: number | null;
  warning_sent_at: string | null;
  expired_notified_at: string | null;
}

export interface EventPackageLimits {
  photos: LimitUsageSummary | null;
  guests: LimitUsageSummary | null;
  gallery: GallerySummary | null;
  can_upload_photos: boolean;
  can_add_guests: boolean;
}

export interface EventPackage {
  id: number;
  event_id?: number;
  package_id?: number;
  used_photos: number;
  used_guests?: number;
  expires_at: string | null;
  package: PackageData | null;
  limits: EventPackageLimits | null;
}

export interface EventStats {
  onlineGuests: number;
  tasksSolved: number;
  latestPhotoAt: string | null;
}

export type FetchEventErrorCode =
  | 'invalid_token'
  | 'token_expired'
  | 'token_revoked'
  | 'token_rate_limited'
  | 'access_rate_limited'
  | 'gallery_expired'
  | 'event_not_public'
  | 'network_error'
  | 'server_error'
  | 'unknown';

interface FetchEventErrorOptions {
  code: FetchEventErrorCode;
  message: string;
  status?: number;
}

export class FetchEventError extends Error {
  readonly code: FetchEventErrorCode;
  readonly status?: number;

  constructor({ code, message, status }: FetchEventErrorOptions) {
    super(message);
    this.name = 'FetchEventError';
    this.code = code;
    this.status = status;
  }
}

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;
}

const API_ERROR_CODES: FetchEventErrorCode[] = [
  'invalid_token',
  'token_expired',
  'token_revoked',
  'token_rate_limited',
  'access_rate_limited',
  'gallery_expired',
  'event_not_public',
];

function resolveErrorCode(rawCode: unknown, status: number): FetchEventErrorCode {
  if (typeof rawCode === 'string') {
    const normalized = rawCode.toLowerCase() as FetchEventErrorCode;
    if ((API_ERROR_CODES as string[]).includes(normalized)) {
      return normalized;
    }
  }

  if (status === 429) return rawCode === 'access_rate_limited' ? 'access_rate_limited' : 'token_rate_limited';
  if (status === 404) return 'event_not_public';
  if (status === 410) return rawCode === 'gallery_expired' ? 'gallery_expired' : 'token_expired';
  if (status === 401) return 'invalid_token';
  if (status === 403) return 'token_revoked';
  if (status >= 500) return 'server_error';

  return 'unknown';
}

function defaultMessageForCode(code: FetchEventErrorCode): string {
  switch (code) {
    case 'invalid_token':
      return 'Der eingegebene Zugriffscode ist ungültig.';
    case 'token_revoked':
      return 'Dieser Zugriffscode wurde deaktiviert. Bitte fordere einen neuen Code an.';
    case 'token_expired':
      return 'Dieser Zugriffscode ist abgelaufen.';
    case 'token_rate_limited':
      return 'Zu viele Versuche in kurzer Zeit. Bitte warte einen Moment und versuche es erneut.';
    case 'access_rate_limited':
      return 'Zu viele Aufrufe in kurzer Zeit. Bitte warte einen Moment und versuche es erneut.';
    case 'gallery_expired':
      return 'Die Galerie ist nicht mehr verfügbar.';
    case 'event_not_public':
      return 'Dieses Event ist nicht öffentlich verfügbar.';
    case 'network_error':
      return 'Keine Verbindung zum Server. Prüfe deine Internetverbindung und versuche es erneut.';
    case 'server_error':
      return 'Der Server ist gerade nicht erreichbar. Bitte versuche es später erneut.';
    case 'unknown':
    default:
      return 'Event konnte nicht geladen werden.';
  }
}

export async function fetchEvent(eventKey: string): Promise<EventData> {
  try {
    const res = await fetch(`/api/v1/events/${encodeURIComponent(eventKey)}`);
    if (!res.ok) {
      let apiMessage: string | null = null;
      let rawCode: unknown;

      try {
        const data = await res.json();
        rawCode = data?.error?.code ?? data?.code;
        const message = data?.error?.message ?? data?.message;
        if (typeof message === 'string' && message.trim() !== '') {
          apiMessage = message.trim();
        }
      } catch {
        // ignore parse errors and fall back to defaults
      }

      const code = resolveErrorCode(rawCode, res.status);
      const message = apiMessage ?? defaultMessageForCode(code);

      throw new FetchEventError({
        code,
        message,
        status: res.status,
      });
    }

    const json = await res.json();

    const moderationMode = json?.live_show?.moderation_mode;
    const normalized: EventData = {
      ...json,
      name: coerceLocalized(json?.name, 'Fotospiel Event'),
      default_locale: typeof json?.default_locale === 'string' && json.default_locale.trim() !== ''
        ? json.default_locale
        : DEFAULT_LOCALE,
      engagement_mode: (json?.engagement_mode as 'tasks' | 'photo_only' | undefined) ?? 'tasks',
      guest_upload_visibility:
        json?.guest_upload_visibility === 'immediate' ? 'immediate' : 'review',
      live_show: {
        moderation_mode: moderationMode === 'off' || moderationMode === 'manual' || moderationMode === 'trusted_only'
          ? moderationMode
          : 'manual',
      },
      demo_read_only: Boolean(json?.demo_read_only),
    };

    if (json?.type) {
      normalized.type = {
        ...json.type,
        name: coerceLocalized(json.type?.name, 'Event'),
      };
    }

    return normalized;
  } catch (error) {
    if (error instanceof FetchEventError) {
      throw error;
    }

    if (error instanceof TypeError) {
      throw new FetchEventError({
        code: 'network_error',
        message: defaultMessageForCode('network_error'),
        status: 0,
      });
    }

    if (error instanceof Error) {
      throw new FetchEventError({
        code: 'unknown',
        message: error.message || defaultMessageForCode('unknown'),
        status: 0,
      });
    }

    throw new FetchEventError({
      code: 'unknown',
      message: defaultMessageForCode('unknown'),
      status: 0,
    });
  }
}

export async function fetchStats(eventKey: string): Promise<EventStats> {
  const res = await fetch(`/api/v1/events/${encodeURIComponent(eventKey)}/stats`, {
    headers: {
      'X-Device-Id': getDeviceId(),
    },
  });
  if (!res.ok) throw new Error('Stats fetch failed');
  const json = await res.json();
  return {
    onlineGuests: json.online_guests ?? json.onlineGuests ?? 0,
    tasksSolved: json.tasks_solved ?? json.tasksSolved ?? 0,
    latestPhotoAt: json.latest_photo_at ?? json.latestPhotoAt ?? null,
  };
}

export async function getEventPackage(eventToken: string): Promise<EventPackage | null> {
  const res = await fetch(`/api/v1/events/${encodeURIComponent(eventToken)}/package`);
  if (!res.ok) {
    if (res.status === 404) return null;
    throw new Error('Failed to load event package');
  }
  const payload = await res.json();
  return {
    ...payload,
    limits: payload?.limits ?? null,
  } as EventPackage;
}
