import {
  CalendarEvent,
  CreateEventDTO,
  UpdateEventDTO,
  ExpandedEvent,
  RecurringDeleteMode,
} from "@calchat/shared";
import { RRule, rrulestr } from "rrule";
import { EventRepository } from "./interfaces";
import { expandRecurringEvents } from "../utils/recurrenceExpander";

export class EventService {
  constructor(private eventRepo: EventRepository) {}

  async create(userId: string, data: CreateEventDTO): Promise<CalendarEvent> {
    return this.eventRepo.create(userId, data);
  }

  async getById(id: string, userId: string): Promise<CalendarEvent | null> {
    const event = await this.eventRepo.findById(id);
    if (!event || event.userId !== userId) {
      return null;
    }
    return event;
  }

  async findByCaldavUUID(
    userId: string,
    caldavUUID: string,
  ): Promise<CalendarEvent | null> {
    return this.eventRepo.findByCaldavUUID(userId, caldavUUID);
  }

  async getAll(userId: string): Promise<CalendarEvent[]> {
    return this.eventRepo.findByUserId(userId);
  }

  async searchByTitle(userId: string, query: string): Promise<CalendarEvent[]> {
    return this.eventRepo.searchByTitle(userId, query);
  }

  async getByDateRange(
    userId: string,
    startDate: Date,
    endDate: Date,
  ): Promise<ExpandedEvent[]> {
    // Get all events for the user
    const allEvents = await this.eventRepo.findByUserId(userId);

    // Separate recurring and non-recurring events
    const recurringEvents = allEvents.filter((e) => e.recurrenceRule);
    const nonRecurringEvents = allEvents.filter((e) => !e.recurrenceRule);

    // Expand all events (recurring get multiple instances, non-recurring stay as-is)
    const expanded = expandRecurringEvents(
      [...nonRecurringEvents, ...recurringEvents],
      startDate,
      endDate,
    );

    return expanded;
  }

  async update(
    id: string,
    userId: string,
    data: UpdateEventDTO,
  ): Promise<CalendarEvent | null> {
    const event = await this.eventRepo.findById(id);
    if (!event || event.userId !== userId) {
      return null;
    }
    return this.eventRepo.update(id, data);
  }

  async delete(id: string, userId: string): Promise<boolean> {
    const event = await this.eventRepo.findById(id);
    if (!event || event.userId !== userId) {
      return false;
    }
    return this.eventRepo.delete(id);
  }

  /**
   * Delete a recurring event with different modes:
   * - 'all': Delete the entire event (all occurrences)
   * - 'single': Add the occurrence date to exception list (EXDATE)
   * - 'future': Set UNTIL in RRULE to stop future occurrences
   *
   * @returns Updated event for 'single'/'future' modes, null for 'all' mode or if not found
   */
  async deleteRecurring(
    id: string,
    userId: string,
    mode: RecurringDeleteMode,
    occurrenceDate?: string,
  ): Promise<CalendarEvent | null> {
    const event = await this.eventRepo.findById(id);
    if (!event || event.userId !== userId) {
      return null;
    }

    // For non-recurring events, always delete completely
    if (!event.recurrenceRule) {
      await this.eventRepo.delete(id);
      return null;
    }

    switch (mode) {
      case "all":
        await this.eventRepo.delete(id);
        return null;

      case "single":
        if (!occurrenceDate) {
          throw new Error("occurrenceDate required for single delete mode");
        }
        // Add to exception dates
        return this.eventRepo.addExceptionDate(id, occurrenceDate);

      case "future":
        if (!occurrenceDate) {
          throw new Error("occurrenceDate required for future delete mode");
        }
        // Check if this is the first occurrence
        const startDateKey = this.formatDateKey(new Date(event.startTime));
        if (occurrenceDate <= startDateKey) {
          // Deleting from first occurrence = delete all
          await this.eventRepo.delete(id);
          return null;
        }
        // Set UNTIL to the day before the occurrence
        const updatedRule = this.addUntilToRRule(
          event.recurrenceRule,
          occurrenceDate,
        );
        return this.eventRepo.update(id, { recurrenceRule: updatedRule });

      default:
        throw new Error(`Unknown delete mode: ${mode}`);
    }
  }

  /**
   * Add or replace UNTIL clause in an RRULE string.
   * The UNTIL is set to 23:59:59 of the day before the occurrence date.
   */
  private addUntilToRRule(ruleString: string, occurrenceDate: string): string {
    // Normalize: ensure we have RRULE: prefix for parsing
    const normalizedRule = ruleString.replace(/^RRULE:/i, "");
    const parsedRule = rrulestr(`RRULE:${normalizedRule}`);

    // Calculate the day before the occurrence at 23:59:59
    const untilDate = new Date(occurrenceDate);
    untilDate.setDate(untilDate.getDate() - 1);
    untilDate.setHours(23, 59, 59, 0);

    // Create new rule with UNTIL, removing COUNT (they're mutually exclusive)
    const newRule = new RRule({
      ...parsedRule.options,
      count: undefined,
      until: untilDate,
    });

    // toString() returns "RRULE:...", we store without prefix
    return newRule.toString().replace(/^RRULE:/, "");
  }

  private formatDateKey(date: Date): string {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
  }
}
