import {
  ChatMessage,
  ChatResponse,
  SendMessageDTO,
  ConversationSummary,
  GetMessagesOptions,
  ProposedEventChange,
  getDay,
  CreateEventDTO,
  UpdateEventDTO,
  EventAction,
  RecurringDeleteMode,
  ConflictingEvent,
} from "@calchat/shared";
import { ChatRepository, AIProvider } from "./interfaces";
import { EventService } from "./EventService";
import { getWeeksOverview, getMonthOverview } from "../utils/eventFormatters";

type TestResponse = {
  content: string;
  proposedChanges?: ProposedEventChange[];
};

// Test response index (cycles through responses)
let responseIndex = 0;

// Static test responses (event proposals)
const staticResponses: TestResponse[] = [
  // {{{
  // === SPORT TEST SCENARIO (3 steps) ===
  // Response 0: Wiederkehrendes Event - jeden Mittwoch Sport
  {
    content:
      "Super! Ich erstelle dir einen wiederkehrenden Termin für Sport jeden Mittwoch:",
    proposedChanges: [
      {
        id: "sport-create",
        action: "create",
        event: {
          title: "Sport",
          startTime: getDay("Wednesday", 1, 18, 0),
          endTime: getDay("Wednesday", 1, 19, 30),
          description: "Wöchentliches Training",
          recurrenceRule: "FREQ=WEEKLY;BYDAY=WE",
        },
      },
    ],
  },
  // Response 1: Ausnahme hinzufügen (2 Wochen später) - DYNAMIC placeholder
  { content: "" },
  // Response 2: UNTIL hinzufügen (nach 6 Wochen) - DYNAMIC placeholder
  { content: "" },
  // Response 3: Weitere Ausnahme in 2 Wochen - DYNAMIC placeholder
  { content: "" },
  // === MULTI-EVENT TEST RESPONSES ===
  // Response 3: 3 Meetings an verschiedenen Tagen
  {
    content: "Alles klar! Ich erstelle dir 3 Team-Meetings für diese Woche:",
    proposedChanges: [
      {
        id: "multi-1-a",
        action: "create",
        event: {
          title: "Team-Meeting Montag",
          startTime: getDay("Monday", 1, 10, 0),
          endTime: getDay("Monday", 1, 11, 0),
          description: "Wöchentliches Standup",
        },
      },
      {
        id: "multi-1-b",
        action: "create",
        event: {
          title: "Team-Meeting Mittwoch",
          startTime: getDay("Wednesday", 1, 10, 0),
          endTime: getDay("Wednesday", 1, 11, 0),
          description: "Sprint Planning",
        },
      },
      {
        id: "multi-1-c",
        action: "create",
        event: {
          title: "Team-Meeting Freitag",
          startTime: getDay("Friday", 1, 10, 0),
          endTime: getDay("Friday", 1, 11, 0),
          description: "Retrospektive",
        },
      },
    ],
  },
  // Response 1: 5 Termine für einen Projekttag
  {
    content: "Ich habe deinen kompletten Projekttag am Dienstag geplant:",
    proposedChanges: [
      {
        id: "multi-2-a",
        action: "create",
        event: {
          title: "Kickoff-Meeting",
          startTime: getDay("Tuesday", 1, 9, 0),
          endTime: getDay("Tuesday", 1, 10, 0),
          description: "Projektstart mit dem Team",
        },
      },
      {
        id: "multi-2-b",
        action: "create",
        event: {
          title: "Design Review",
          startTime: getDay("Tuesday", 1, 10, 30),
          endTime: getDay("Tuesday", 1, 11, 30),
          description: "UI/UX Besprechung",
        },
      },
      {
        id: "multi-2-c",
        action: "create",
        event: {
          title: "Mittagspause",
          startTime: getDay("Tuesday", 1, 12, 0),
          endTime: getDay("Tuesday", 1, 13, 0),
          description: "Team-Lunch",
        },
      },
      {
        id: "multi-2-d",
        action: "create",
        event: {
          title: "Tech Review",
          startTime: getDay("Tuesday", 1, 14, 0),
          endTime: getDay("Tuesday", 1, 15, 30),
          description: "Architektur-Diskussion",
        },
      },
      {
        id: "multi-2-e",
        action: "create",
        event: {
          title: "Wrap-up",
          startTime: getDay("Tuesday", 1, 16, 0),
          endTime: getDay("Tuesday", 1, 16, 30),
          description: "Zusammenfassung und nächste Schritte",
        },
      },
    ],
  },
  // Response 2: 2 wiederkehrende Termine
  {
    content: "Ich erstelle dir zwei wiederkehrende Fitness-Termine:",
    proposedChanges: [
      {
        id: "multi-3-a",
        action: "create",
        event: {
          title: "Yoga",
          startTime: getDay("Monday", 1, 7, 0),
          endTime: getDay("Monday", 1, 8, 0),
          description: "Morgen-Yoga",
          recurrenceRule: "FREQ=WEEKLY;BYDAY=MO,WE,FR",
        },
      },
      {
        id: "multi-3-b",
        action: "create",
        event: {
          title: "Laufen",
          startTime: getDay("Tuesday", 1, 18, 0),
          endTime: getDay("Tuesday", 1, 19, 0),
          description: "Abendlauf im Park",
          recurrenceRule: "FREQ=WEEKLY;BYDAY=TU,TH",
        },
      },
    ],
  },
  // === ORIGINAL RESPONSES ===
  // Response 3: Help response (text only)
  {
    content:
      "Ich bin dein Kalender-Assistent! Du kannst mir einfach sagen, welche Termine du erstellen, ändern oder löschen möchtest. Zum Beispiel:\n\n" +
      '• "Erstelle einen Termin für morgen um 15 Uhr"\n' +
      '• "Was habe ich nächste Woche vor?"\n' +
      '• "Verschiebe das Meeting auf Donnerstag"\n\n' +
      "Wie kann ich dir helfen?",
  },
  // Response 1: Meeting mit Jens - next Friday 14:00
  {
    content:
      "Alles klar! Ich erstelle dir einen Termin für das Meeting mit Jens am nächsten Freitag um 14:00 Uhr:",
    proposedChanges: [
      {
        id: "test-1",
        action: "create",
        event: {
          title: "Meeting mit Jens",
          startTime: getDay("Friday", 1, 14, 0),
          endTime: getDay("Friday", 1, 15, 0),
          description: "Arbeitstreffen",
        },
      },
    ],
  },
  // Response 2: Recurring event - every Saturday 10:00
  {
    content:
      "Verstanden! Ich erstelle einen wiederkehrenden Termin: Jeden Samstag um 10:00 Uhr Badezimmer putzen:",
    proposedChanges: [
      {
        id: "test-2",
        action: "create",
        event: {
          title: "Badezimmer putzen",
          startTime: getDay("Saturday", 1, 10, 0),
          endTime: getDay("Saturday", 1, 11, 0),
          recurrenceRule: "FREQ=WEEKLY;BYDAY=SA",
        },
      },
    ],
  },
  // Response 3: 2-week overview (DYNAMIC - placeholder)
  { content: "" },
  // Response 4: Delete "Meeting mit Jens" (DYNAMIC - placeholder)
  { content: "" },
  // Response 5: Doctor appointment with description
  {
    content:
      "Ich habe dir einen Arzttermin eingetragen. Denk daran, deine Versichertenkarte mitzunehmen!",
    proposedChanges: [
      {
        id: "test-5",
        action: "create",
        event: {
          title: "Arzttermin Dr. Müller",
          startTime: getDay("Wednesday", 1, 9, 30),
          endTime: getDay("Wednesday", 1, 10, 30),
          description:
            "Routineuntersuchung - Versichertenkarte nicht vergessen",
        },
      },
    ],
  },
  // Response 6: Birthday - yearly recurring
  {
    content:
      "Geburtstage vergisst man leicht - aber nicht mit mir! Ich habe Mamas Geburtstag eingetragen:",
    proposedChanges: [
      {
        id: "test-6",
        action: "create",
        event: {
          title: "Mamas Geburtstag",
          startTime: getDay("Thursday", 2, 0, 0),
          endTime: getDay("Thursday", 2, 23, 59),
          recurrenceRule: "FREQ=YEARLY",
        },
      },
    ],
  },
  // Response 7: Gym - recurring for 2 months (8 weeks)
  {
    content:
      "Perfekt! Ich habe dein Probetraining eingetragen - jeden Dienstag für die nächsten 2 Monate:",
    proposedChanges: [
      {
        id: "test-7",
        action: "create",
        event: {
          title: "Fitnessstudio Probetraining",
          startTime: getDay("Tuesday", 1, 18, 0),
          endTime: getDay("Tuesday", 1, 19, 30),
          recurrenceRule: "FREQ=WEEKLY;BYDAY=TU;COUNT=8",
        },
      },
    ],
  },
  // Response 8: 1-week overview (DYNAMIC - placeholder)
  { content: "" },
  // Response 9: Phone call - short appointment (Wednesday, so +2 days = Friday)
  {
    content:
      "Alles klar! Ich habe das Telefonat mit deiner Mutter eingetragen:",
    proposedChanges: [
      {
        id: "test-9",
        action: "create",
        event: {
          title: "Telefonat mit Mama",
          startTime: getDay("Wednesday", 1, 11, 0),
          endTime: getDay("Wednesday", 1, 11, 30),
        },
      },
    ],
  },
  // Response 10: Update "Telefonat mit Mama" +3 days (DYNAMIC - placeholder)
  { content: "" },
  // Response 11: Birthday party - evening event
  {
    content: "Super! Die Geburtstagsfeier ist eingetragen. Viel Spaß!",
    proposedChanges: [
      {
        id: "test-11",
        action: "create",
        event: {
          title: "Geburtstagsfeier Lisa",
          startTime: getDay("Saturday", 2, 19, 0),
          endTime: getDay("Saturday", 2, 23, 0),
          description: "Geschenk: Buch über Fotografie",
        },
      },
    ],
  },
  // Response 12: Language course - limited to 8 weeks (Thu + Sat)
  {
    content:
      "Dein Spanischkurs ist eingetragen! Er läuft jeden Donnerstag und Samstag für die nächsten 8 Wochen:",
    proposedChanges: [
      {
        id: "test-12",
        action: "create",
        event: {
          title: "Spanischkurs VHS",
          startTime: getDay("Thursday", 1, 19, 0),
          endTime: getDay("Thursday", 1, 20, 30),
          recurrenceRule: "FREQ=WEEKLY;BYDAY=TH,SA;COUNT=8",
        },
      },
    ],
  },
  // Response 13: Monthly overview (DYNAMIC - placeholder)
  { content: "" },
  // }}}
];

async function getTestResponse(
  index: number,
  eventService: EventService,
  userId: string,
): Promise<TestResponse> {
  const responseIdx = index % staticResponses.length;

  // === SPORT TEST SCENARIO (Dynamic responses) ===
  // Response 1: Add exception to "Sport" (2 weeks later)
  if (responseIdx === 1) {
    const events = await eventService.getAll(userId);
    const sportEvent = events.find((e) => e.title === "Sport");
    if (sportEvent) {
      // Calculate date 2 weeks from the first occurrence
      const exceptionDate = new Date(sportEvent.startTime);
      exceptionDate.setDate(exceptionDate.getDate() + 14);
      const exceptionDateStr = exceptionDate.toISOString().split("T")[0];

      return {
        content:
          "Verstanden! Ich füge eine Ausnahme für den Sport-Termin in 2 Wochen hinzu:",
        proposedChanges: [
          {
            id: "sport-exception",
            action: "delete",
            eventId: sportEvent.id,
            deleteMode: "single",
            occurrenceDate: exceptionDateStr,
            event: {
              title: sportEvent.title,
              startTime: exceptionDate,
              endTime: new Date(exceptionDate.getTime() + 90 * 60 * 1000), // +90 min
              description: sportEvent.description,
              recurrenceRule: sportEvent.recurrenceRule,
              exceptionDates: sportEvent.exceptionDates,
            },
          },
        ],
      };
    }
    return {
      content:
        "Ich konnte keinen Termin 'Sport' finden. Bitte erstelle ihn zuerst.",
    };
  }

  // Response 2: Add UNTIL to "Sport" (after 6 weeks total)
  if (responseIdx === 2) {
    const events = await eventService.getAll(userId);
    const sportEvent = events.find((e) => e.title === "Sport");
    if (sportEvent) {
      // Calculate UNTIL date: 6 weeks from start
      const untilDate = new Date(sportEvent.startTime);
      untilDate.setDate(untilDate.getDate() + 42); // 6 weeks
      const untilStr =
        untilDate.toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";

      const newRule = `FREQ=WEEKLY;BYDAY=WE;UNTIL=${untilStr}`;

      return {
        content: "Alles klar! Ich beende die Sport-Serie nach 6 Wochen:",
        proposedChanges: [
          {
            id: "sport-until",
            action: "update",
            eventId: sportEvent.id,
            updates: { recurrenceRule: newRule },
            event: {
              title: sportEvent.title,
              startTime: sportEvent.startTime,
              endTime: sportEvent.endTime,
              description: sportEvent.description,
              recurrenceRule: newRule,
              exceptionDates: sportEvent.exceptionDates,
            },
          },
        ],
      };
    }
    return {
      content:
        "Ich konnte keinen Termin 'Sport' finden. Bitte erstelle ihn zuerst.",
    };
  }

  // Response 3: Add another exception to "Sport" (2 weeks after the first exception)
  if (responseIdx === 3) {
    const events = await eventService.getAll(userId);
    const sportEvent = events.find((e) => e.title === "Sport");
    if (sportEvent) {
      // Calculate date 4 weeks from the first occurrence (2 weeks after the first exception)
      const exceptionDate = new Date(sportEvent.startTime);
      exceptionDate.setDate(exceptionDate.getDate() + 28); // 4 weeks
      const exceptionDateStr = exceptionDate.toISOString().split("T")[0];

      return {
        content:
          "Alles klar! Ich füge eine weitere Ausnahme für den Sport-Termin hinzu:",
        proposedChanges: [
          {
            id: "sport-exception-2",
            action: "delete",
            eventId: sportEvent.id,
            deleteMode: "single",
            occurrenceDate: exceptionDateStr,
            event: {
              title: sportEvent.title,
              startTime: exceptionDate,
              endTime: new Date(exceptionDate.getTime() + 90 * 60 * 1000), // +90 min
              description: sportEvent.description,
              recurrenceRule: sportEvent.recurrenceRule,
              exceptionDates: sportEvent.exceptionDates,
            },
          },
        ],
      };
    }
    return {
      content:
        "Ich konnte keinen Termin 'Sport' finden. Bitte erstelle ihn zuerst.",
    };
  }

  // Dynamic responses: fetch events from DB and format
  // (Note: indices shifted by +3 due to new sport responses)
  if (responseIdx === 6) {
    return { content: await getWeeksOverview(eventService, userId, 2) };
  }

  if (responseIdx === 7) {
    // Delete "Meeting mit Jens"
    const events = await eventService.getAll(userId);
    const jensEvent = events.find((e) => e.title === "Meeting mit Jens");
    if (jensEvent) {
      return {
        content: "Soll ich diesen Termin wirklich löschen?",
        proposedChanges: [
          {
            id: "test-4",
            action: "delete",
            eventId: jensEvent.id,
            event: {
              title: jensEvent.title,
              startTime: jensEvent.startTime,
              endTime: jensEvent.endTime,
              description: jensEvent.description,
            },
          },
        ],
      };
    }
    return { content: "Ich konnte keinen Termin 'Meeting mit Jens' finden." };
  }

  if (responseIdx === 11) {
    return { content: await getWeeksOverview(eventService, userId, 1) };
  }

  if (responseIdx === 13) {
    // Update "Telefonat mit Mama" +3 days and change time to 13:00
    const events = await eventService.getAll(userId);
    const mamaEvent = events.find((e) => e.title === "Telefonat mit Mama");
    if (mamaEvent) {
      const newStart = new Date(mamaEvent.startTime);
      newStart.setDate(newStart.getDate() + 3);
      newStart.setHours(13, 0, 0, 0);
      const newEnd = new Date(newStart);
      newEnd.setMinutes(30);
      return {
        content:
          "Alles klar, ich verschiebe das Telefonat mit Mama auf Samstag um 13:00 Uhr:",
        proposedChanges: [
          {
            id: "test-10",
            action: "update",
            eventId: mamaEvent.id,
            updates: { startTime: newStart, endTime: newEnd },
            // Include event with new times for display
            event: {
              title: mamaEvent.title,
              startTime: newStart,
              endTime: newEnd,
              description: mamaEvent.description,
            },
          },
        ],
      };
    }
    return { content: "Ich konnte keinen Termin 'Telefonat mit Mama' finden." };
  }

  if (responseIdx === 16) {
    const now = new Date();
    return {
      content: await getMonthOverview(
        eventService,
        userId,
        now.getFullYear(),
        now.getMonth(),
      ),
    };
  }

  return staticResponses[responseIdx];
}

export class ChatService {
  constructor(
    private chatRepo: ChatRepository,
    private eventService: EventService,
    private aiProvider: AIProvider,
  ) {}

  async processMessage(
    userId: string,
    data: SendMessageDTO,
  ): Promise<ChatResponse> {
    let conversationId = data.conversationId;
    if (!conversationId) {
      const conversation = await this.chatRepo.createConversation(userId);
      conversationId = conversation.id;
    }

    // Save user message
    await this.chatRepo.createMessage(conversationId, {
      sender: "user",
      content: data.content,
    });

    let response: TestResponse;

    if (process.env.USE_TEST_RESPONSES === "true") {
      // Test mode: use static responses
      response = await getTestResponse(
        responseIndex,
        this.eventService,
        userId,
      );
      responseIndex++;
    } else {
      // Production mode: use real AI
      const history = await this.chatRepo.getMessages(conversationId, {
        limit: 20,
      });

      response = await this.aiProvider.processMessage(data.content, {
        userId,
        conversationHistory: history,
        currentDate: new Date(),
        fetchEventsInRange: async (start, end) => {
          return this.eventService.getByDateRange(userId, start, end);
        },
        searchEvents: async (query) => {
          return this.eventService.searchByTitle(userId, query);
        },
        fetchEventById: async (eventId) => {
          return this.eventService.getById(eventId, userId);
        },
      });
    }

    // Save and then return assistant response
    const answerMessage = await this.chatRepo.createMessage(conversationId, {
      sender: "assistant",
      content: response.content,
      proposedChanges: response.proposedChanges,
    });

    return { message: answerMessage, conversationId: conversationId };
  }

  async confirmEvent(
    userId: string,
    conversationId: string,
    messageId: string,
    proposalId: string,
    action: EventAction,
    event?: CreateEventDTO,
    eventId?: string,
    updates?: UpdateEventDTO,
    deleteMode?: RecurringDeleteMode,
    occurrenceDate?: string,
  ): Promise<ChatResponse> {
    // Update specific proposal with respondedAction
    await this.chatRepo.updateProposalResponse(
      messageId,
      proposalId,
      "confirm",
    );

    // Perform the actual event operation
    let content: string;

    if (action === "create" && event) {
      const createdEvent = await this.eventService.create(userId, event);
      content = `Der Termin "${createdEvent.title}" wurde erstellt.`;
    } else if (action === "update" && eventId && updates) {
      const updatedEvent = await this.eventService.update(
        eventId,
        userId,
        updates,
      );
      content = updatedEvent
        ? `Der Termin "${updatedEvent.title}" wurde aktualisiert.`
        : "Termin nicht gefunden.";
    } else if (action === "delete" && eventId) {
      // Use deleteRecurring for proper handling of recurring events
      const mode = deleteMode || "all";
      await this.eventService.deleteRecurring(
        eventId,
        userId,
        mode,
        occurrenceDate,
      );

      // Build appropriate response message
      let deleteDescription = "";
      if (deleteMode === "single") {
        deleteDescription = " (dieses Vorkommen)";
      } else if (deleteMode === "future") {
        deleteDescription = " (dieses und zukünftige Vorkommen)";
      }
      content = event?.title
        ? `Der Termin "${event.title}"${deleteDescription} wurde gelöscht.`
        : `Der Termin${deleteDescription} wurde gelöscht.`;
    } else {
      content = "Ungültige Aktion.";
    }

    // Save response message to DB
    const message = await this.chatRepo.createMessage(conversationId, {
      sender: "assistant",
      content,
    });

    return { message, conversationId };
  }

  async rejectEvent(
    userId: string,
    conversationId: string,
    messageId: string,
    proposalId: string,
  ): Promise<ChatResponse> {
    // Update specific proposal with respondedAction
    await this.chatRepo.updateProposalResponse(messageId, proposalId, "reject");

    // Save response message to DB
    const message = await this.chatRepo.createMessage(conversationId, {
      sender: "assistant",
      content: "Der Vorschlag wurde abgelehnt.",
    });

    return { message, conversationId };
  }

  async getConversations(userId: string): Promise<ConversationSummary[]> {
    const conversations = await this.chatRepo.getConversationsByUser(userId);

    // For each conversation, get the last message
    const summaries: ConversationSummary[] = await Promise.all(
      conversations.map(async (conv) => {
        const messages = await this.chatRepo.getMessages(conv.id, { limit: 1 });
        return {
          id: conv.id,
          lastMessage: messages[0],
          createdAt: conv.createdAt,
        };
      }),
    );

    return summaries;
  }

  async getConversation(
    userId: string,
    conversationId: string,
    options?: GetMessagesOptions,
  ): Promise<ChatMessage[]> {
    // Verify conversation belongs to user
    const conversations = await this.chatRepo.getConversationsByUser(userId);
    const conversation = conversations.find((c) => c.id === conversationId);

    if (!conversation) {
      throw new Error("Conversation not found");
    }

    return this.chatRepo.getMessages(conversationId, options);
  }

  async updateProposalEvent(
    messageId: string,
    proposalId: string,
    event: CreateEventDTO,
  ): Promise<ChatMessage | null> {
    // Get the message to find the conversation
    const message = await this.chatRepo.getMessageById(messageId);
    if (!message) {
      return null;
    }

    // Get the conversation to find the userId
    const conversation = await this.chatRepo.getConversationById(
      message.conversationId,
    );
    if (!conversation) {
      return null;
    }
    const userId = conversation.userId;

    // Get event times
    const eventStart = new Date(event.startTime);
    const eventEnd = new Date(event.endTime);

    // Get day range for conflict checking
    const dayStart = new Date(eventStart);
    dayStart.setHours(0, 0, 0, 0);
    const dayEnd = new Date(dayStart);
    dayEnd.setDate(dayStart.getDate() + 1);

    // Fetch events for the day
    const dayEvents = await this.eventService.getByDateRange(
      userId,
      dayStart,
      dayEnd,
    );

    // Check for time overlaps (use occurrenceStart/End for expanded recurring events)
    const conflicts: ConflictingEvent[] = dayEvents
      .filter(
        (e) =>
          new Date(e.occurrenceStart) < eventEnd &&
          new Date(e.occurrenceEnd) > eventStart,
      )
      .map((e) => ({
        title: e.title,
        startTime: new Date(e.occurrenceStart),
        endTime: new Date(e.occurrenceEnd),
      }));

    return this.chatRepo.updateProposalEvent(
      messageId,
      proposalId,
      event,
      conflicts.length > 0 ? conflicts : undefined,
    );
  }
}
