import {
  fallbackCafePageImages,
  fallbackContacts,
  fallbackDocuments,
  fallbackEvents,
  fallbackFuelPrices,
  fallbackGiftShopImages,
  fallbackHomepageBannerImages,
  fallbackHomepageVolunteerImages,
  fallbackNews,
  fallbackNotices,
  type ContactItem,
  type DocumentItem,
  type EventItem,
  type FuelPrice,
  type HomepageBannerImage,
  type NewsItem,
  type Notice,
} from './fallback-data';

type CollectionName = 'news' | 'events' | 'notices' | 'fuel_prices' | 'documents' | 'contacts';

const defaultSortByCollection: Partial<Record<CollectionName, string>> = {
  news: '-publish_date',
  events: '-start_datetime',
  notices: '-priority',
  fuel_prices: 'fuel_type',
  documents: '-uploaded_at',
  contacts: 'order',
};

declare const process: {
  env: Record<string, string | undefined>;
};

const directusUrl = process.env.DIRECTUS_URL ?? 'http://directus:8055';
const directusPublicUrl = process.env.DIRECTUS_PUBLIC_URL && !process.env.DIRECTUS_PUBLIC_URL.includes('example.com')
  ? process.env.DIRECTUS_PUBLIC_URL
  : directusUrl;
const directusAssetBaseUrl = process.env.DIRECTUS_ASSET_BASE_URL && !process.env.DIRECTUS_ASSET_BASE_URL.includes('example.com')
  ? process.env.DIRECTUS_ASSET_BASE_URL
  : undefined;
const directusAssetUrlTemplate =
  process.env.DIRECTUS_ASSET_URL_TEMPLATE && !process.env.DIRECTUS_ASSET_URL_TEMPLATE.includes('example.com')
    ? process.env.DIRECTUS_ASSET_URL_TEMPLATE
    : undefined;
const directusToken = process.env.DIRECTUS_TOKEN ?? process.env.DIRECTUS_ADMIN_TOKEN;
const directusDebug = ['1', 'true', 'yes', 'on'].includes((process.env.DIRECTUS_DEBUG ?? '').toLowerCase());
const homepageBannerFolder = process.env.DIRECTUS_HOMEPAGE_BANNER_FOLDER ?? 'homepage-banners';
const homepageVolunteersFolder = process.env.DIRECTUS_HOMEPAGE_VOLUNTEERS_FOLDER ?? 'homepage-volunteers';
const cafePageFolder = process.env.DIRECTUS_CAFE_PAGE_FOLDER ?? 'cafe-page';
const giftShopFolder = process.env.DIRECTUS_GIFT_SHOP_FOLDER ?? 'gift-shop';

type DirectusFolder = {
  id: string;
  name: string;
};

type DirectusFile = {
  id: string;
  title?: string;
  description?: string;
  filename_download?: string;
  filename_disk?: string;
  type?: string;
  tags?: string[] | string | null;
};

type ImageFolderOptions = {
  firstTag?: string;
  shuffleRest?: boolean;
};

type EventTemplateRecord = {
  id: number;
  title?: string;
  slug?: string;
  description?: string;
  image?: string | DirectusFile;
  logo?: string | DirectusFile;
  booking_url?: string;
};

type EventDateRecord = {
  id: number;
  date?: string;
  template?: EventTemplateRecord | number | null;
};

function directusHeaders(): Record<string, string> | undefined {
  if (!directusToken) return undefined;
  return { Authorization: `Bearer ${directusToken}` };
}

function directusLog(message: string): void {
  if (!directusDebug) return;
  console.warn(`[directus] ${message}`);
}

function directusEndpointLabel(endpoint: URL): string {
  return `${endpoint.pathname}${endpoint.search}`;
}

async function readCollection<T>(collection: CollectionName): Promise<T[]> {
  const endpoint = new URL(`/items/${collection}`, directusUrl);
  endpoint.searchParams.set('limit', '100');
  const sort = defaultSortByCollection[collection];
  if (sort) {
    endpoint.searchParams.set('sort', sort);
  }

  try {
    const response = await fetch(endpoint, {
      headers: directusHeaders(),
    });
    if (!response.ok) {
      throw new Error(`Directus responded with ${response.status}`);
    }

    const payload = (await response.json()) as { data?: T[] };
    const data = payload.data ?? [];
    directusLog(`collection ${collection}: read ${data.length} item(s)`);
    return data;
  } catch (error) {
    directusLog(`collection ${collection}: using fallback after ${error instanceof Error ? error.message : String(error)}`);
    return fallbackFor(collection) as T[];
  }
}

async function readDirectusEndpoint<T>(endpoint: URL): Promise<T[]> {
  directusLog(`GET ${directusEndpointLabel(endpoint)}`);
  let response = await fetch(endpoint, {
    headers: directusHeaders(),
  });

  if (response.status === 403 && directusToken) {
    directusLog(`GET ${directusEndpointLabel(endpoint)} returned 403 with token; retrying without token`);
    response = await fetch(endpoint);
  }

  if (!response.ok) {
    let detail = '';
    try {
      const payload = (await response.json()) as { errors?: Array<{ message?: string; extensions?: { code?: string; reason?: string } }> };
      const firstError = payload.errors?.[0];
      detail = firstError?.extensions?.reason || firstError?.message || '';
    } catch {
      detail = '';
    }

    throw new Error(`Directus responded with ${response.status}${detail ? `: ${detail}` : ''}`);
  }

  const payload = (await response.json()) as { data?: T[] };
  const data = payload.data ?? [];
  directusLog(`GET ${directusEndpointLabel(endpoint)} returned ${data.length} item(s)`);
  return data;
}

function extensionFromFilename(filename?: string): string {
  if (!filename) return '';

  const lastSegment = filename.split('/').pop() ?? '';
  const dotIndex = lastSegment.lastIndexOf('.');

  if (dotIndex <= 0 || dotIndex === lastSegment.length - 1) return '';
  return lastSegment.slice(dotIndex);
}

function directusFileId(file: string | DirectusFile): string {
  return typeof file === 'string' ? file : file.id;
}

function directusObjectKey(file: string | DirectusFile): string {
  if (typeof file !== 'string' && file.filename_disk) {
    return file.filename_disk;
  }

  const fileId = directusFileId(file);
  return `${fileId}${extensionFromFilename(typeof file === 'string' ? undefined : file.filename_download)}`;
}

function fileTags(file: DirectusFile): string[] {
  if (Array.isArray(file.tags)) {
    return file.tags;
  }

  if (typeof file.tags === 'string') {
    return file.tags
      .split(',')
      .map((tag) => tag.trim())
      .filter(Boolean);
  }

  return [];
}

function hasTag(file: DirectusFile, tag: string): boolean {
  const targetTag = tag.toLowerCase();
  return fileTags(file).some((fileTag) => fileTag.toLowerCase() === targetTag);
}

function shuffleFiles(files: DirectusFile[]): DirectusFile[] {
  const shuffled = [...files];

  for (let index = shuffled.length - 1; index > 0; index -= 1) {
    const swapIndex = Math.floor(Math.random() * (index + 1));
    [shuffled[index], shuffled[swapIndex]] = [shuffled[swapIndex], shuffled[index]];
  }

  return shuffled;
}

function orderImageFiles(files: DirectusFile[], options: ImageFolderOptions): DirectusFile[] {
  const { firstTag, shuffleRest } = options;

  if (!firstTag) {
    return shuffleRest ? shuffleFiles(files) : files;
  }

  const firstFiles = files.filter((file) => hasTag(file, firstTag));
  const restFiles = files.filter((file) => !hasTag(file, firstTag));
  return [...firstFiles, ...(shuffleRest ? shuffleFiles(restFiles) : restFiles)];
}

export function resolveDirectusAssetUrl(file: string | DirectusFile): string {
  const fileId = directusFileId(file);
  const r2ObjectKey = directusObjectKey(file);
  const encodedObjectKey = encodeURIComponent(r2ObjectKey);

  if (directusAssetUrlTemplate) {
    return directusAssetUrlTemplate
      .replaceAll('{fileId}', encodedObjectKey)
      .replaceAll('{id}', encodedObjectKey)
      .replaceAll('{key}', encodedObjectKey);
  }

  if (directusAssetBaseUrl) {
    const baseUrl = directusAssetBaseUrl.endsWith('/') ? directusAssetBaseUrl : `${directusAssetBaseUrl}/`;
    return new URL(encodedObjectKey, baseUrl).toString();
  }

  return new URL(`/assets/${fileId}`, directusPublicUrl).toString();
}

async function findFolderByName(name: string): Promise<DirectusFolder | null> {
  const endpoint = new URL('/folders', directusUrl);
  endpoint.searchParams.set('limit', '1');
  endpoint.searchParams.set('filter[name][_eq]', name);
  endpoint.searchParams.set('fields', 'id,name');

  const folders = await readDirectusEndpoint<DirectusFolder>(endpoint);
  return folders[0] ?? null;
}

async function getImagesFromFolder(
  folderName: string,
  fallbackImages: HomepageBannerImage[],
  options: ImageFolderOptions = {},
): Promise<HomepageBannerImage[]> {
  try {
    const folder = await findFolderByName(folderName);
    if (!folder) {
      directusLog(`folder "${folderName}": not found; using ${fallbackImages.length} fallback image(s)`);
      return fallbackImages;
    }

    const endpoint = new URL('/files', directusUrl);
    endpoint.searchParams.set('limit', '20');
    endpoint.searchParams.set('sort', '-uploaded_on');
    endpoint.searchParams.set('filter[folder][_eq]', folder.id);
    endpoint.searchParams.set('filter[type][_starts_with]', 'image/');
    endpoint.searchParams.set('fields', 'id,title,description,filename_download,filename_disk,type,tags');

    const files = await readDirectusEndpoint<DirectusFile>(endpoint);
    const orderedFiles = orderImageFiles(files, options);
    const images = orderedFiles.map((file) => ({
      src: resolveDirectusAssetUrl(file),
      alt: file.description || file.title || file.filename_download || 'Swansea Airport',
    }));

    if (images.length === 0) {
      directusLog(`folder "${folderName}": no images found; using ${fallbackImages.length} fallback image(s)`);
      return fallbackImages;
    }

    directusLog(`folder "${folderName}": using ${images.length} Directus image(s)`);
    return images;
  } catch (error) {
    directusLog(`folder "${folderName}": using fallback after ${error instanceof Error ? error.message : String(error)}`);
    return fallbackImages;
  }
}

export const getHomepageBannerImages = () =>
  getImagesFromFolder(homepageBannerFolder, fallbackHomepageBannerImages, {
    firstTag: 'first',
    shuffleRest: true,
  });
export const getHomepageVolunteerImages = () =>
  getImagesFromFolder(homepageVolunteersFolder, fallbackHomepageVolunteerImages, {
    shuffleRest: true,
  });
export const getCafePageImages = () => getImagesFromFolder(cafePageFolder, fallbackCafePageImages);
export const getGiftShopImages = () => getImagesFromFolder(giftShopFolder, fallbackGiftShopImages);

function stripHtml(value = ''): string {
  return value
    .replace(/<[^>]*>/g, ' ')
    .replace(/&nbsp;/g, ' ')
    .replace(/&amp;/g, '&')
    .replace(/&rsquo;/g, "'")
    .replace(/&lsquo;/g, "'")
    .replace(/&ldquo;/g, '"')
    .replace(/&rdquo;/g, '"')
    .replace(/\s+/g, ' ')
    .trim();
}

function mapEventDateToEventItem(eventDate: EventDateRecord): EventItem | null {
  if (!eventDate.date || !eventDate.template || typeof eventDate.template === 'number') return null;

  const template = eventDate.template;
  if (!template.title || !template.slug) return null;

  const description = template.description ?? '';
  const summary = stripHtml(description).slice(0, 180);

  return {
    id: `${template.id}-${eventDate.id}`,
    date_id: eventDate.id,
    template_id: template.id,
    title: template.title,
    slug: template.slug,
    summary: summary ? `${summary}${summary.length === 180 ? '...' : ''}` : undefined,
    description,
    start_datetime: eventDate.date,
    realimage: template.image,
    logo: template.logo ? resolveDirectusAssetUrl(template.logo) : undefined,
    registration_link: template.booking_url,
  };
}

async function getRecurringEvents(): Promise<EventItem[]> {
  const endpoint = new URL('/items/event_dates', directusUrl);
  endpoint.searchParams.set('limit', '100');
  endpoint.searchParams.set('sort', 'date');
  endpoint.searchParams.set(
    'fields',
    'id,date,template.id,template.title,template.slug,template.description,template.image.id,template.image.filename_download,template.image.filename_disk,template.logo.id,template.logo.filename_download,template.logo.filename_disk,template.booking_url',
  );

  const eventDates = await readDirectusEndpoint<EventDateRecord>(endpoint);
  return eventDates.map(mapEventDateToEventItem).filter((event): event is EventItem => event !== null);
}

function fallbackFor(collection: CollectionName) {
  switch (collection) {
    case 'news':
      return fallbackNews;
    case 'events':
      return fallbackEvents;
    case 'notices':
      return fallbackNotices;
    case 'fuel_prices':
      return fallbackFuelPrices;
    case 'documents':
      return fallbackDocuments;
    case 'contacts':
      return fallbackContacts;
  }
}

export const getNews = () => readCollection<NewsItem>('news');
export async function getEvents(): Promise<EventItem[]> {
  try {
    const recurringEvents = await getRecurringEvents();
    return recurringEvents.length > 0 ? recurringEvents : readCollection<EventItem>('events');
  } catch {
    return readCollection<EventItem>('events');
  }
}
export const getNotices = () => readCollection<Notice>('notices');
export const getFuelPrices = () => readCollection<FuelPrice>('fuel_prices');
export const getDocuments = () => readCollection<DocumentItem>('documents');
export const getContacts = () => readCollection<ContactItem>('contacts');
