import { NextApiRequest, NextApiResponse } from 'next';
import { fetchPageBlock, revalidatePage } from 'src/utils/api';
import fetch from 'node-fetch';
import pool from 'src/utils/db';
import { formatDateRange } from 'src/utils/formatDateRange';
import dateFormatter from 'src/utils/dateFormatter';

interface IAPIConfig {
  label: string;
  apiUrl: string;
  apiKey: string;
  country: string;
}

interface IEvent {
  id: string;
  title: string;
  description: string;
  image_profile: string;
  beginDate: string;
  endDate: string;
  dateRange: string;
  industry: string;
  website: string;
  location: string;
  country: string;
  city?: string;
  ufi: boolean;
}

const fetchAPIConfigs = async (): Promise<IAPIConfig[]> => {
  const apiConfigs = await fetchPageBlock('events', 'api');
  return JSON.parse(apiConfigs.content).apis;
};

const mapApiEvent = (event: any, country: string): IEvent => ({
  id: `${event.projectID}`,
  title: event.title,
  description: event.description,
  image_profile: event.image_profile,
  beginDate: dateFormatter(event.beginDate),
  endDate: dateFormatter(event.endDate),
  dateRange: formatDateRange(event),
  location: event.location,
  industry: event.industry,
  website: event.website,
  country: event.country ? event.country : country,
  city: event.city ? event.city : '',
  ufi: event.ufi ? event.ufi : false,
});

const fetchDataFromAPI = async (
  apiUrl: string,
  apiKey: string
): Promise<IEvent[]> => {
  const response = await fetch(apiUrl, {
    method: 'POST',
    body: JSON.stringify({
      apiKey: apiKey,
      lang: 'en',
      projectID: 0,
    }),
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  const data: any = await response.json();

  if (!Array.isArray(data?.confList)) {
    throw new Error('Invalid API response: confList is missing or not an array');
  }

  return data.confList;
};

const loadExistingEventsFromDB = async (): Promise<IEvent[]> => {
  try {
    const [rows] = await pool.execute(
      `SELECT content FROM page_events WHERE block_name = 'events'`
    );
    const content = (rows as { content: string }[])[0]?.content;
    if (!content) return [];
    return JSON.parse(content) as IEvent[];
  } catch (error) {
    console.error('Failed to load existing events from DB:', error);
    return [];
  }
};

const groupEventsByCountry = (events: IEvent[]): Record<string, IEvent[]> =>
  events.reduce<Record<string, IEvent[]>>((acc, event) => {
    const key = event.country;
    acc[key] = acc[key] || [];
    acc[key].push(event);
    return acc;
  }, {});

type CountryFetchStatus = {
  country: string;
  status: 'updated' | 'fallback';
  error?: string;
};

const fetchAllEvents = async (
  apiConfigs: IAPIConfig[],
  existingEvents: IEvent[]
): Promise<{ allEvents: Record<string, IEvent[]>; statuses: CountryFetchStatus[] }> => {
  const oldByCountry = groupEventsByCountry(existingEvents);
  const resultByCountry: Record<string, IEvent[]> = { ...oldByCountry };
  const statuses: CountryFetchStatus[] = [];

  const configsByCountry = apiConfigs.reduce<Record<string, IAPIConfig[]>>((acc, config) => {
    acc[config.country] = acc[config.country] || [];
    acc[config.country].push(config);
    return acc;
  }, {});

  for (const [country, configs] of Object.entries(configsByCountry)) {
    let hasError = false;
    let errorMessage = '';
    const newEvents: IEvent[] = [];

    for (const { apiUrl, apiKey } of configs) {
      try {
        const eventsFromAPI = await fetchDataFromAPI(apiUrl, apiKey);
        newEvents.push(
          ...eventsFromAPI.map((event: any) => mapApiEvent(event, country))
        );
      } catch (error) {
        hasError = true;
        errorMessage = error instanceof Error ? error.message : String(error);
        console.error(`API fetch failed for ${country} (${apiUrl}):`, error);
        break;
      }
    }

    if (hasError) {
      resultByCountry[country] = oldByCountry[country] || [];
      statuses.push({
        country,
        status: 'fallback',
        error: errorMessage,
      });
    } else {
      resultByCountry[country] = newEvents.sort((a, b) =>
        a.beginDate.localeCompare(b.beginDate)
      );
      statuses.push({ country, status: 'updated' });
    }
  }

  return { allEvents: resultByCountry, statuses };
};

const saveEventsToDB = async (allEvents: Record<string, IEvent[]>) => {
  const combinedEvents = Object.entries(allEvents)
    .flatMap(([country, events]) => events as IEvent[])
    .sort(
      (a: any, b: any) =>
        new Date(a.beginDate).getTime() - new Date(b.beginDate).getTime()
    );
  try {
    await pool.execute(
      `UPDATE page_events SET content = ? WHERE block_name = 'events'`,
      [JSON.stringify(combinedEvents)]
    );

    return true;
  } catch (error) {
    console.error('Error during data update:', error);
    return false;
  }
};

const FetchEvents = async (_req: NextApiRequest, res: NextApiResponse) => {
  try {
    const apiConfigs = await fetchAPIConfigs();
    const existingEvents = await loadExistingEventsFromDB();

    const { allEvents, statuses } = await fetchAllEvents(apiConfigs, existingEvents);

    const isSaved = await saveEventsToDB(allEvents);

    const isCombined = await (
      await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/combineEvents`)
    ).json();

    await revalidatePage('/events');
    await revalidatePage(`/`);

    const failedCountries = statuses.filter((s) => s.status === 'fallback');

    if (isSaved && isCombined) {
      res.status(200).json({
        message: 'Events fetched and saved successfully!',
        statuses,
        failedCountries: failedCountries.map((s) => s.country),
      });
    } else {
      res.status(500).json({
        error: 'Internal Server Error',
        message: 'Failed to save data.',
        statuses,
      });
    }
  } catch (err) {
    console.error('An error occurred:', err);
    res.status(500).json({
      error: 'Internal Server Error',
      message: err instanceof Error ? err.message : String(err),
    });
  }
};

export default FetchEvents;
