import { db } from '../config/database.js';
import { events, eventUsers, users, todos, todoUsers, companyReminders, companyUsers, companies } from '../db/schema.js';
import { eq, and, gte, lt, desc, inArray, sql, ne } from 'drizzle-orm';
import { NotFoundError } from '../utils/errors.js';

/**
 * Parse datetime-local string to Date object
 * datetime-local format: "2024-01-15T12:00" (no timezone)
 * Interprets as Europe/Bratislava timezone
 */
const parseLocalDateTime = (dateTimeString) => {
  if (!dateTimeString) return null;

  // Ak už má timezone info, parsuj priamo
  if (dateTimeString.includes('Z') || /[+-]\d{2}:\d{2}$/.test(dateTimeString)) {
    return new Date(dateTimeString);
  }

  // Pre datetime-local input (bez timezone) - interpretuj ako Europe/Bratislava
  let normalized = dateTimeString;
  if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(dateTimeString)) {
    normalized = `${dateTimeString}:00`;
  }

  const [datePart] = normalized.split('T');
  const [year, month, day] = datePart.split('-').map(Number);

  // Detekcia letného času pre Európu
  const isDST = month > 3 && month < 10 ||
    (month === 3 && day >= 25) ||
    (month === 10 && day < 25);

  const offset = isDST ? '+02:00' : '+01:00';
  const isoString = `${normalized}${offset}`;

  return new Date(isoString);
};

/**
 * Format event timestamps to ISO string with timezone
 */
const formatEventOutput = (event, assignedUsers = []) => {
  if (!event) return null;

  return {
    ...event,
    start: event.start instanceof Date ? event.start.toISOString() : event.start,
    end: event.end instanceof Date ? event.end.toISOString() : event.end,
    createdAt: event.createdAt instanceof Date ? event.createdAt.toISOString() : event.createdAt,
    updatedAt: event.updatedAt instanceof Date ? event.updatedAt.toISOString() : event.updatedAt,
    assignedUsers,
  };
};

/**
 * Get assigned users for events
 */
const getAssignedUsersForEvents = async (eventIds) => {
  if (!eventIds || eventIds.length === 0) return {};

  const assignedUsersData = await db
    .select({
      eventId: eventUsers.eventId,
      userId: users.id,
      username: users.username,
      firstName: users.firstName,
      lastName: users.lastName,
    })
    .from(eventUsers)
    .innerJoin(users, eq(eventUsers.userId, users.id))
    .where(inArray(eventUsers.eventId, eventIds));

  // Group by eventId
  const usersByEventId = {};
  for (const row of assignedUsersData) {
    if (!usersByEventId[row.eventId]) {
      usersByEventId[row.eventId] = [];
    }
    usersByEventId[row.eventId].push({
      id: row.userId,
      username: row.username,
      firstName: row.firstName,
      lastName: row.lastName,
    });
  }

  return usersByEventId;
};

/**
 * Get event IDs accessible to user (member sees only assigned events)
 */
const getAccessibleEventIds = async (userId) => {
  const userEvents = await db
    .select({ eventId: eventUsers.eventId })
    .from(eventUsers)
    .where(eq(eventUsers.userId, userId));

  return userEvents.map((row) => row.eventId);
};

/**
 * Get calendar data - events and todos for month
 * Filtered by user access (admin sees all, member sees only assigned)
 */
export const getCalendarData = async (year, month, userId, isAdmin) => {
  // Month boundaries
  const startOfMonthStr = `${year}-${String(month).padStart(2, '0')}-01T00:00`;
  const endMonth = month === 12 ? 1 : month + 1;
  const endYear = month === 12 ? year + 1 : year;
  const endOfMonthStr = `${endYear}-${String(endMonth).padStart(2, '0')}-01T00:00`;

  const startOfMonth = parseLocalDateTime(startOfMonthStr);
  const endOfMonth = parseLocalDateTime(endOfMonthStr);

  // Get events
  let monthEvents;
  if (isAdmin) {
    // Admin sees all events
    monthEvents = await db
      .select()
      .from(events)
      .where(
        and(
          gte(events.start, startOfMonth),
          lt(events.start, endOfMonth)
        )
      )
      .orderBy(desc(events.start));
  } else {
    // Member sees only assigned events
    const accessibleEventIds = await getAccessibleEventIds(userId);
    if (accessibleEventIds.length === 0) {
      monthEvents = [];
    } else {
      monthEvents = await db
        .select()
        .from(events)
        .where(
          and(
            gte(events.start, startOfMonth),
            lt(events.start, endOfMonth),
            inArray(events.id, accessibleEventIds)
          )
        )
        .orderBy(desc(events.start));
    }
  }

  // Get assigned users for events
  const eventIds = monthEvents.map((e) => e.id);
  const usersByEventId = await getAssignedUsersForEvents(eventIds);

  const formattedEvents = monthEvents.map((event) =>
    formatEventOutput(event, usersByEventId[event.id] || [])
  );

  // Get todos for month (with dueDate in this month)
  let monthTodos;
  if (isAdmin) {
    monthTodos = await db
      .select()
      .from(todos)
      .where(
        and(
          gte(todos.dueDate, startOfMonth),
          lt(todos.dueDate, endOfMonth)
        )
      )
      .orderBy(desc(todos.dueDate));
  } else {
    // Member sees only assigned todos
    const userTodos = await db
      .select({ todoId: todoUsers.todoId })
      .from(todoUsers)
      .where(eq(todoUsers.userId, userId));

    const todoIds = userTodos.map((row) => row.todoId);

    if (todoIds.length === 0) {
      monthTodos = [];
    } else {
      monthTodos = await db
        .select()
        .from(todos)
        .where(
          and(
            gte(todos.dueDate, startOfMonth),
            lt(todos.dueDate, endOfMonth),
            inArray(todos.id, todoIds)
          )
        )
        .orderBy(desc(todos.dueDate));
    }
  }

  // Format todos for calendar
  const formattedTodos = monthTodos.map((todo) => ({
    ...todo,
    dueDate: todo.dueDate instanceof Date ? todo.dueDate.toISOString() : todo.dueDate,
    createdAt: todo.createdAt instanceof Date ? todo.createdAt.toISOString() : todo.createdAt,
    updatedAt: todo.updatedAt instanceof Date ? todo.updatedAt.toISOString() : todo.updatedAt,
  }));

  // Get company reminders for month
  let accessibleCompanyIds = null;
  if (!isAdmin) {
    // Member sees only reminders from companies they are assigned to
    const userCompanies = await db
      .select({ companyId: companyUsers.companyId })
      .from(companyUsers)
      .where(eq(companyUsers.userId, userId));
    accessibleCompanyIds = userCompanies.map((row) => row.companyId);
  }

  let monthReminders = [];
  if (isAdmin || (accessibleCompanyIds && accessibleCompanyIds.length > 0)) {
    const reminderConditions = [
      gte(companyReminders.dueDate, startOfMonth),
      lt(companyReminders.dueDate, endOfMonth),
      eq(companyReminders.isChecked, false),
    ];

    if (!isAdmin && accessibleCompanyIds) {
      reminderConditions.push(inArray(companyReminders.companyId, accessibleCompanyIds));
    }

    monthReminders = await db
      .select({
        id: companyReminders.id,
        companyId: companyReminders.companyId,
        description: companyReminders.description,
        dueDate: companyReminders.dueDate,
        isChecked: companyReminders.isChecked,
        createdAt: companyReminders.createdAt,
        companyName: companies.name,
      })
      .from(companyReminders)
      .innerJoin(companies, eq(companyReminders.companyId, companies.id))
      .where(and(...reminderConditions))
      .orderBy(desc(companyReminders.dueDate));
  }

  // Format reminders for calendar
  const formattedReminders = monthReminders.map((reminder) => ({
    ...reminder,
    dueDate: reminder.dueDate instanceof Date ? reminder.dueDate.toISOString() : reminder.dueDate,
    createdAt: reminder.createdAt instanceof Date ? reminder.createdAt.toISOString() : reminder.createdAt,
  }));

  return {
    events: formattedEvents,
    todos: formattedTodos,
    reminders: formattedReminders,
  };
};

/**
 * Get event by ID
 */
export const getEventById = async (eventId, userId, isAdmin) => {
  const [event] = await db
    .select()
    .from(events)
    .where(eq(events.id, eventId))
    .limit(1);

  if (!event) {
    throw new NotFoundError('Event nenájdený');
  }

  // Check access for non-admin
  if (!isAdmin) {
    const accessibleEventIds = await getAccessibleEventIds(userId);
    if (!accessibleEventIds.includes(eventId)) {
      throw new NotFoundError('Event nenájdený');
    }
  }

  const usersByEventId = await getAssignedUsersForEvents([eventId]);
  return formatEventOutput(event, usersByEventId[eventId] || []);
};

/**
 * Create a new event
 */
export const createEvent = async (userId, data) => {
  const { title, description, type, start, end, assignedUserIds } = data;

  // Create event
  const [newEvent] = await db
    .insert(events)
    .values({
      title,
      description: description || null,
      type: type || 'meeting',
      start: parseLocalDateTime(start),
      end: parseLocalDateTime(end),
      createdBy: userId,
    })
    .returning();

  // Always assign creator + any additional users
  const allUserIds = new Set([userId]);
  if (assignedUserIds && Array.isArray(assignedUserIds)) {
    assignedUserIds.forEach((id) => allUserIds.add(id));
  }

  // Insert event users
  const eventUserInserts = Array.from(allUserIds).map((uid) => ({
    eventId: newEvent.id,
    userId: uid,
  }));

  await db.insert(eventUsers).values(eventUserInserts);

  // Get assigned users for response
  const usersByEventId = await getAssignedUsersForEvents([newEvent.id]);
  return formatEventOutput(newEvent, usersByEventId[newEvent.id] || []);
};

/**
 * Update an event
 */
export const updateEvent = async (eventId, data) => {
  // Check if event exists
  const [existingEvent] = await db
    .select()
    .from(events)
    .where(eq(events.id, eventId))
    .limit(1);

  if (!existingEvent) {
    throw new NotFoundError('Event nenájdený');
  }

  const { title, description, type, start, end, assignedUserIds } = data;

  const updateData = {
    updatedAt: new Date(),
  };

  if (title !== undefined) updateData.title = title;
  if (description !== undefined) updateData.description = description;
  if (type !== undefined) updateData.type = type;
  if (start !== undefined) updateData.start = parseLocalDateTime(start);
  if (end !== undefined) updateData.end = parseLocalDateTime(end);

  const [updated] = await db
    .update(events)
    .set(updateData)
    .where(eq(events.id, eventId))
    .returning();

  // Update assigned users if provided
  if (assignedUserIds !== undefined) {
    // Delete existing assignments
    await db.delete(eventUsers).where(eq(eventUsers.eventId, eventId));

    // Always include creator
    const allUserIds = new Set([existingEvent.createdBy]);
    if (Array.isArray(assignedUserIds)) {
      assignedUserIds.forEach((id) => allUserIds.add(id));
    }

    // Insert new assignments
    const eventUserInserts = Array.from(allUserIds)
      .filter((id) => id) // filter out null
      .map((uid) => ({
        eventId: eventId,
        userId: uid,
      }));

    if (eventUserInserts.length > 0) {
      await db.insert(eventUsers).values(eventUserInserts);
    }
  }

  const usersByEventId = await getAssignedUsersForEvents([eventId]);
  return formatEventOutput(updated, usersByEventId[eventId] || []);
};

/**
 * Delete an event
 */
export const deleteEvent = async (eventId) => {
  const [event] = await db
    .select()
    .from(events)
    .where(eq(events.id, eventId))
    .limit(1);

  if (!event) {
    throw new NotFoundError('Event nenájdený');
  }

  // eventUsers will be deleted automatically due to CASCADE
  await db.delete(events).where(eq(events.id, eventId));

  return { success: true, message: 'Event bol zmazaný' };
};

/**
 * Get count of events and todos for today
 * Used for calendar badge in sidebar
 */
export const getTodayCalendarCount = async (userId, isAdmin) => {
  const today = new Date();
  const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
  const endOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);

  // Get event count for today
  let eventCount;
  if (isAdmin) {
    const eventResult = await db
      .select({ count: sql`count(*)::int` })
      .from(events)
      .where(
        and(
          gte(events.start, startOfDay),
          lt(events.start, endOfDay)
        )
      );
    eventCount = eventResult[0]?.count || 0;
  } else {
    const accessibleEventIds = await getAccessibleEventIds(userId);
    if (accessibleEventIds.length === 0) {
      eventCount = 0;
    } else {
      const eventResult = await db
        .select({ count: sql`count(*)::int` })
        .from(events)
        .where(
          and(
            gte(events.start, startOfDay),
            lt(events.start, endOfDay),
            inArray(events.id, accessibleEventIds)
          )
        );
      eventCount = eventResult[0]?.count || 0;
    }
  }

  // Get todo count for today
  let todoCount;
  if (isAdmin) {
    const todoResult = await db
      .select({ count: sql`count(*)::int` })
      .from(todos)
      .where(
        and(
          gte(todos.dueDate, startOfDay),
          lt(todos.dueDate, endOfDay),
          ne(todos.status, 'completed')
        )
      );
    todoCount = todoResult[0]?.count || 0;
  } else {
    const userTodos = await db
      .select({ todoId: todoUsers.todoId })
      .from(todoUsers)
      .where(eq(todoUsers.userId, userId));

    const todoIds = userTodos.map((row) => row.todoId);

    if (todoIds.length === 0) {
      todoCount = 0;
    } else {
      const todoResult = await db
        .select({ count: sql`count(*)::int` })
        .from(todos)
        .where(
          and(
            gte(todos.dueDate, startOfDay),
            lt(todos.dueDate, endOfDay),
            ne(todos.status, 'completed'),
            inArray(todos.id, todoIds)
          )
        );
      todoCount = todoResult[0]?.count || 0;
    }
  }

  // Get reminder count for today (uncompleted reminders with dueDate today)
  let reminderCount;
  if (isAdmin) {
    const reminderResult = await db
      .select({ count: sql`count(*)::int` })
      .from(companyReminders)
      .where(
        and(
          gte(companyReminders.dueDate, startOfDay),
          lt(companyReminders.dueDate, endOfDay),
          eq(companyReminders.isChecked, false)
        )
      );
    reminderCount = reminderResult[0]?.count || 0;
  } else {
    // For non-admin users, only count reminders for companies they have access to
    const userCompanyIds = await db
      .select({ companyId: companyUsers.companyId })
      .from(companyUsers)
      .where(eq(companyUsers.userId, userId));

    const companyIds = userCompanyIds.map((row) => row.companyId);

    if (companyIds.length === 0) {
      reminderCount = 0;
    } else {
      const reminderResult = await db
        .select({ count: sql`count(*)::int` })
        .from(companyReminders)
        .where(
          and(
            gte(companyReminders.dueDate, startOfDay),
            lt(companyReminders.dueDate, endOfDay),
            eq(companyReminders.isChecked, false),
            inArray(companyReminders.companyId, companyIds)
          )
        );
      reminderCount = reminderResult[0]?.count || 0;
    }
  }

  return {
    eventCount,
    todoCount,
    reminderCount,
    totalCount: eventCount + todoCount + reminderCount,
  };
};
