import {
  ProposedEventChange,
  getDay,
  Day,
  DAY_TO_GERMAN,
  RecurringDeleteMode,
} from "@calchat/shared";
import { AIContext } from "../../services/interfaces";
import { formatDate, formatTime, formatDateTime } from "@calchat/shared";

/**
 * Check if two time ranges overlap.
 */
function hasTimeOverlap(
  start1: Date,
  end1: Date,
  start2: Date,
  end2: Date,
): boolean {
  return start1 < end2 && end1 > start2;
}

/**
 * Proposed change without ID - ID is added by GPTAdapter when collecting proposals
 */
type ToolProposedChange = Omit<ProposedEventChange, "id" | "respondedAction">;

/**
 * Result of executing a tool call.
 */
export interface ToolResult {
  content: string;
  proposedChange?: ToolProposedChange;
}

/**
 * Execute a tool call and return the result.
 * This function is provider-agnostic and can be used with any LLM.
 * Async to support tools that need to fetch data (e.g., getEventsInRange).
 */
export async function executeToolCall(
  name: string,
  args: Record<string, unknown>,
  context: AIContext,
): Promise<ToolResult> {
  switch (name) {
    case "getDay": {
      const date = getDay(
        args.day as Day,
        args.offset as number,
        args.hour as number,
        args.minute as number,
      );
      const dayName = DAY_TO_GERMAN[args.day as Day];
      return {
        content: `${date.toISOString()} (${dayName}, ${formatDate(date)} um ${formatTime(date)} Uhr)`,
      };
    }

    case "getCurrentDateTime": {
      const now = context.currentDate;
      return {
        content: `${now.toISOString()} (${formatDateTime(now)})`,
      };
    }

    case "proposeCreateEvent": {
      const event = {
        title: args.title as string,
        startTime: new Date(args.startTime as string),
        endTime: new Date(args.endTime as string),
        description: args.description as string | undefined,
        recurrenceRule: args.recurrenceRule as string | undefined,
      };
      const dateStr = formatDate(event.startTime);
      const startStr = formatTime(event.startTime);
      const endStr = formatTime(event.endTime);

      // Check for conflicts - fetch events for the specific day
      const dayStart = new Date(event.startTime);
      dayStart.setHours(0, 0, 0, 0);
      const dayEnd = new Date(dayStart);
      dayEnd.setDate(dayStart.getDate() + 1);

      const dayEvents = await context.fetchEventsInRange(dayStart, dayEnd);

      // Use occurrenceStart/occurrenceEnd for expanded recurring events
      const conflicts = dayEvents.filter((e) =>
        hasTimeOverlap(
          event.startTime,
          event.endTime,
          new Date(e.occurrenceStart),
          new Date(e.occurrenceEnd),
        ),
      );

      // Build conflict warning if any
      let conflictWarning = "";
      if (conflicts.length > 0) {
        const conflictNames = conflicts.map((c) => `"${c.title}"`).join(", ");
        conflictWarning = `\n⚠️ ACHTUNG: Zeitkonflikt mit ${conflictNames}!`;
      }

      return {
        content: `Event-Vorschlag erstellt: "${event.title}" am ${dateStr} von ${startStr} bis ${endStr} Uhr${conflictWarning}`,
        proposedChange: {
          action: "create",
          event,
          conflictingEvents:
            conflicts.length > 0
              ? conflicts.map((c) => ({
                  title: c.title,
                  startTime: new Date(c.occurrenceStart),
                  endTime: new Date(c.occurrenceEnd),
                }))
              : undefined,
        },
      };
    }

    case "proposeUpdateEvent": {
      const eventId = args.eventId as string;
      const existingEvent = await context.fetchEventById(eventId);

      if (!existingEvent) {
        return { content: `Event mit ID ${eventId} nicht gefunden.` };
      }

      const updates: Record<string, unknown> = {};
      if (args.title) updates.title = args.title;
      if (args.startTime)
        updates.startTime = new Date(args.startTime as string);
      if (args.endTime) updates.endTime = new Date(args.endTime as string);
      if (args.description) updates.description = args.description;
      if (args.recurrenceRule) updates.recurrenceRule = args.recurrenceRule;

      // Build event object for display (merge existing with updates)
      const displayEvent = {
        title: (updates.title as string) || existingEvent.title,
        startTime: (updates.startTime as Date) || existingEvent.startTime,
        endTime: (updates.endTime as Date) || existingEvent.endTime,
        description:
          (updates.description as string) || existingEvent.description,
        recurrenceRule:
          (updates.recurrenceRule as string) || existingEvent.recurrenceRule,
        exceptionDates: existingEvent.exceptionDates,
      };

      return {
        content: `Update-Vorschlag für "${existingEvent.title}" erstellt.`,
        proposedChange: {
          action: "update",
          eventId,
          updates,
          event: displayEvent,
        },
      };
    }

    case "proposeDeleteEvent": {
      const eventId = args.eventId as string;
      const deleteMode = (args.deleteMode as RecurringDeleteMode) || "all";
      const occurrenceDate = args.occurrenceDate as string | undefined;
      const existingEvent = await context.fetchEventById(eventId);

      if (!existingEvent) {
        return { content: `Event mit ID ${eventId} nicht gefunden.` };
      }

      // Build descriptive content based on delete mode
      let modeDescription = "";
      if (existingEvent.recurrenceRule) {
        switch (deleteMode) {
          case "single":
            modeDescription = " (nur dieses Vorkommen)";
            break;
          case "future":
            modeDescription = " (dieses und alle zukünftigen Vorkommen)";
            break;
          case "all":
            modeDescription = " (alle Vorkommen)";
            break;
        }
      }

      return {
        content: `Lösch-Vorschlag für "${existingEvent.title}"${modeDescription} erstellt.`,
        proposedChange: {
          action: "delete",
          eventId,
          event: {
            title: existingEvent.title,
            startTime: existingEvent.startTime,
            endTime: existingEvent.endTime,
            description: existingEvent.description,
            recurrenceRule: existingEvent.recurrenceRule,
            exceptionDates: existingEvent.exceptionDates,
          },
          deleteMode: existingEvent.recurrenceRule ? deleteMode : undefined,
          occurrenceDate: existingEvent.recurrenceRule
            ? occurrenceDate
            : undefined,
        },
      };
    }

    case "searchEvents": {
      const query = args.query as string;
      const matches = await context.searchEvents(query);

      if (matches.length === 0) {
        return { content: `Keine Termine mit "${query}" gefunden.` };
      }

      const results = matches
        .map((e) => {
          const start = new Date(e.startTime);
          const recurrenceInfo = e.recurrenceRule ? " (wiederkehrend)" : "";
          return `- ${e.title} (ID: ${e.id}) am ${formatDate(start)} um ${formatTime(start)} Uhr${recurrenceInfo}`;
        })
        .join("\n");

      return { content: `Gefundene Termine:\n${results}` };
    }

    case "getEventsInRange": {
      const startDate = new Date(args.startDate as string);
      const endDate = new Date(args.endDate as string);
      const events = await context.fetchEventsInRange(startDate, endDate);

      if (events.length === 0) {
        return { content: "Keine Termine in diesem Zeitraum." };
      }

      const eventsText = events
        .map((e) => {
          const start = new Date(e.occurrenceStart);
          const recurrenceInfo = e.recurrenceRule ? " (wiederkehrend)" : "";
          return `- ${e.title} (ID: ${e.id}) am ${formatDate(start)} um ${formatTime(start)} Uhr${recurrenceInfo}`;
        })
        .join("\n");

      return {
        content: `Termine von ${formatDate(startDate)} bis ${formatDate(endDate)}:\n${eventsText}`,
      };
    }

    default:
      return { content: `Unbekannte Funktion: ${name}` };
  }
}
