import { db } from '../config/database.js';
import { timeEntries, projects, todos, companies, users, timesheets } from '../db/schema.js';
import { eq, and, gte, lte, desc } from 'drizzle-orm';
import { NotFoundError, BadRequestError, ForbiddenError } from '../utils/errors.js';
import ExcelJS from 'exceljs';
import fs from 'fs/promises';
import path from 'path';
import {
  logTimerStarted, logTimerStopped, logTimerPaused, logTimerResumed,
  logTimeEntryUpdated, logTimeEntryDeleted,
} from './audit.service.js';

// Helpers to normalize optional payload fields
const normalizeOptionalId = (value) => {
  if (value === undefined) return undefined;
  if (value === null || value === '') return null;
  return value;
};

const normalizeOptionalText = (value) => {
  if (value === undefined) return undefined;
  if (value === null) return null;
  if (typeof value !== 'string') return value;
  const trimmed = value.trim();
  return trimmed.length ? trimmed : null;
};

const formatDate = (value) => {
  const date = new Date(value);
  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}`;
};

const formatTime = (value) => {
  const date = new Date(value);
  const hours = String(date.getHours()).padStart(2, '0');
  const minutes = String(date.getMinutes()).padStart(2, '0');
  return `${hours}:${minutes}`;
};

const formatDuration = (minutes) => {
  if (!Number.isFinite(minutes)) return '';
  const hours = Math.floor(minutes / 60);
  const mins = Math.abs(minutes % 60);
  return `${hours}:${String(mins).padStart(2, '0')}`;
};

// --- Shared entity validation helpers ---

const validateProjectExists = async (projectId) => {
  const [project] = await db
    .select()
    .from(projects)
    .where(eq(projects.id, projectId))
    .limit(1);
  if (!project) throw new NotFoundError('Projekt nenájdený');
};

const validateTodoExists = async (todoId) => {
  const [todo] = await db
    .select()
    .from(todos)
    .where(eq(todos.id, todoId))
    .limit(1);
  if (!todo) throw new NotFoundError('Todo nenájdené');
};

const validateCompanyExists = async (companyId) => {
  const [company] = await db
    .select()
    .from(companies)
    .where(eq(companies.id, companyId))
    .limit(1);
  if (!company) throw new NotFoundError('Firma nenájdená');
  return company;
};

const validateRelatedEntities = async (projectId, todoId, companyId) => {
  if (projectId) await validateProjectExists(projectId);
  if (todoId) await validateTodoExists(todoId);
  if (companyId) await validateCompanyExists(companyId);
};

// --- Shared timesheet generation helpers ---

const getUserNamePrefix = (user) => {
  if (user.firstName && user.lastName) {
    return `${user.firstName}-${user.lastName}`.toLowerCase().replace(/\s+/g, '-');
  }
  if (user.firstName) {
    return user.firstName.toLowerCase().replace(/\s+/g, '-');
  }
  if (user.username) {
    return user.username.toLowerCase().replace(/\s+/g, '-');
  }
  return 'timesheet';
};

const computeDailyTotals = (entries) => {
  let totalMinutes = 0;
  const dailyTotals = {};
  entries.forEach((entry) => {
    const minutes = entry.duration || 0;
    totalMinutes += minutes;
    const day = formatDate(entry.startTime);
    dailyTotals[day] = (dailyTotals[day] || 0) + minutes;
  });
  return { totalMinutes, dailyTotals };
};

const generateTimesheetWorkbook = ({ title, headerMeta, columns, headerRowNumber, entries, formatRow }) => {
  const workbook = new ExcelJS.Workbook();
  workbook.creator = 'crm-server';
  const worksheet = workbook.addWorksheet(title, {
    views: [{ state: 'frozen', ySplit: headerRowNumber }],
  });

  // Title
  worksheet.getCell('A1').value = title;
  worksheet.getCell('A1').font = { name: 'Calibri', size: 16, bold: true };
  worksheet.mergeCells('A1:D1');

  // Meta rows (Name, Period, etc.)
  headerMeta.forEach((line, i) => {
    worksheet.getCell(`A${i + 2}`).value = line;
  });

  worksheet.columns = columns;

  const headerRow = worksheet.getRow(headerRowNumber);
  headerRow.values = columns.map((c) => c.header);
  headerRow.font = { name: 'Calibri', size: 11, bold: true };
  headerRow.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
  headerRow.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE8EEF5' } };
  headerRow.height = 20;

  entries.forEach((entry) => {
    worksheet.addRow(formatRow(entry));
  });

  // Style body rows
  worksheet.eachRow((row, rowNumber) => {
    if (rowNumber >= headerRowNumber) {
      row.alignment = { vertical: 'middle', wrapText: true };
    }
  });

  return { workbook, worksheet };
};

const addDailySummary = (worksheet, dailyTotals, totalMinutes) => {
  const summaryStart = worksheet.lastRow.number + 2;
  worksheet.getCell(`A${summaryStart}`).value = 'Daily totals';
  worksheet.getCell(`A${summaryStart}`).font = { name: 'Calibri', size: 12, bold: true };
  worksheet.mergeCells(`A${summaryStart}:B${summaryStart}`);

  const dailyHeaderRowNumber = summaryStart + 1;
  const dailyHeaderRow = worksheet.getRow(dailyHeaderRowNumber);
  dailyHeaderRow.values = ['Date', 'Total (h:mm)'];
  dailyHeaderRow.font = { name: 'Calibri', size: 11, bold: true };
  dailyHeaderRow.alignment = { vertical: 'middle', horizontal: 'center' };
  dailyHeaderRow.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE8EEF5' } };

  Object.keys(dailyTotals)
    .sort()
    .forEach((day) => {
      worksheet.addRow([day, formatDuration(dailyTotals[day])]);
    });

  const overallRow = worksheet.addRow(['Overall total', formatDuration(totalMinutes)]);
  overallRow.font = { name: 'Calibri', size: 11, bold: true };
};

const saveTimesheetFile = async (workbook, { userId, year, month, filename }) => {
  const uploadsDir = path.join(
    process.cwd(),
    'uploads',
    'timesheets',
    userId.toString(),
    year.toString(),
    month.toString()
  );
  await fs.mkdir(uploadsDir, { recursive: true });

  const filePath = path.join(uploadsDir, filename);
  let savedFilePath = null;

  try {
    await workbook.xlsx.writeFile(filePath);
    savedFilePath = filePath;
    const { size: fileSize } = await fs.stat(filePath);

    const [newTimesheet] = await db
      .insert(timesheets)
      .values({
        userId,
        fileName: filename,
        filePath,
        fileType: 'xlsx',
        fileSize,
        year,
        month,
        isGenerated: true,
      })
      .returning();

    return newTimesheet;
  } catch (error) {
    if (savedFilePath) {
      await fs.unlink(savedFilePath).catch(() => {});
    }
    throw error;
  }
};

// --- Public API ---

/**
 * Start a new time entry
 */
export const startTimeEntry = async (userId, data, auditContext = null) => {
  const projectId = normalizeOptionalId(data.projectId);
  const todoId = normalizeOptionalId(data.todoId);
  const companyId = normalizeOptionalId(data.companyId);
  const description = normalizeOptionalText(data.description);

  // Check if user already has a running time entry
  const [existingRunning] = await db
    .select()
    .from(timeEntries)
    .where(and(eq(timeEntries.userId, userId), eq(timeEntries.isRunning, true)))
    .limit(1);

  // Automatically stop existing running timer
  if (existingRunning) {
    const endTime = new Date();
    let totalPausedSeconds = existingRunning.pausedDuration || 0;
    if (existingRunning.pausedAt) {
      totalPausedSeconds += Math.floor((endTime - new Date(existingRunning.pausedAt)) / 1000);
    }
    const durationInMinutes = Math.round(((endTime - new Date(existingRunning.startTime)) / 60000) - (totalPausedSeconds / 60));

    await db
      .update(timeEntries)
      .set({
        endTime,
        duration: Math.max(0, durationInMinutes),
        isRunning: false,
        pausedAt: null,
        updatedAt: new Date(),
      })
      .where(eq(timeEntries.id, existingRunning.id));
  }

  await validateRelatedEntities(projectId, todoId, companyId);

  const [newEntry] = await db
    .insert(timeEntries)
    .values({
      userId,
      projectId: projectId ?? null,
      todoId: todoId ?? null,
      companyId: companyId ?? null,
      description: description ?? null,
      startTime: new Date(),
      endTime: null,
      duration: null,
      isRunning: true,
      isEdited: false,
    })
    .returning();

  if (auditContext) {
    await logTimerStarted(auditContext.userId, newEntry.id, data.description || 'Timer', auditContext.ipAddress, auditContext.userAgent);
  }

  return newEntry;
};

/**
 * Stop a running time entry
 */
export const stopTimeEntry = async (entryId, userId, data = {}, auditContext = null) => {
  const projectId = normalizeOptionalId(data.projectId);
  const todoId = normalizeOptionalId(data.todoId);
  const companyId = normalizeOptionalId(data.companyId);
  const description = normalizeOptionalText(data.description);

  const entry = await getTimeEntryById(entryId);

  if (entry.userId !== userId) {
    throw new BadRequestError('Nemáte oprávnenie zastaviť tento časovač');
  }

  if (!entry.isRunning) {
    throw new BadRequestError('Tento časovač už nie je spustený');
  }

  const endTime = new Date();
  let totalPausedSeconds = entry.pausedDuration || 0;
  if (entry.pausedAt) {
    totalPausedSeconds += Math.floor((endTime - new Date(entry.pausedAt)) / 1000);
  }
  const durationInMinutes = Math.round(((endTime - new Date(entry.startTime)) / 60000) - (totalPausedSeconds / 60));

  await validateRelatedEntities(projectId, todoId, companyId);

  const [updated] = await db
    .update(timeEntries)
    .set({
      endTime,
      duration: Math.max(0, durationInMinutes),
      isRunning: false,
      pausedAt: null,
      projectId: projectId !== undefined ? projectId : entry.projectId,
      todoId: todoId !== undefined ? todoId : entry.todoId,
      companyId: companyId !== undefined ? companyId : entry.companyId,
      description: description !== undefined ? description : entry.description,
      updatedAt: new Date(),
    })
    .where(eq(timeEntries.id, entryId))
    .returning();

  if (auditContext) {
    await logTimerStopped(auditContext.userId, updated.id, data.description || 'Timer', updated.duration, auditContext.ipAddress, auditContext.userAgent);
  }

  return updated;
};

/**
 * Pause a running time entry
 */
export const pauseTimeEntry = async (entryId, userId, auditContext = null) => {
  const entry = await getTimeEntryById(entryId);

  if (entry.userId !== userId) {
    throw new BadRequestError('Nemáte oprávnenie pozastaviť tento časovač');
  }

  if (!entry.isRunning) {
    throw new BadRequestError('Tento časovač nie je spustený');
  }

  if (entry.pausedAt) {
    throw new BadRequestError('Tento časovač je už pozastavený');
  }

  const [updated] = await db
    .update(timeEntries)
    .set({
      pausedAt: new Date(),
      updatedAt: new Date(),
    })
    .where(eq(timeEntries.id, entryId))
    .returning();

  if (auditContext) {
    await logTimerPaused(auditContext.userId, updated.id, auditContext.ipAddress, auditContext.userAgent);
  }

  return updated;
};

/**
 * Resume a paused time entry
 */
export const resumeTimeEntry = async (entryId, userId, auditContext = null) => {
  const entry = await getTimeEntryById(entryId);

  if (entry.userId !== userId) {
    throw new BadRequestError('Nemáte oprávnenie obnoviť tento časovač');
  }

  if (!entry.isRunning) {
    throw new BadRequestError('Tento časovač nie je spustený');
  }

  if (!entry.pausedAt) {
    throw new BadRequestError('Tento časovač nie je pozastavený');
  }

  const now = new Date();
  const pauseSeconds = Math.floor((now - new Date(entry.pausedAt)) / 1000);
  const newPausedDuration = (entry.pausedDuration || 0) + pauseSeconds;

  const [updated] = await db
    .update(timeEntries)
    .set({
      pausedAt: null,
      pausedDuration: newPausedDuration,
      updatedAt: new Date(),
    })
    .where(eq(timeEntries.id, entryId))
    .returning();

  if (auditContext) {
    await logTimerResumed(auditContext.userId, updated.id, auditContext.ipAddress, auditContext.userAgent);
  }

  return updated;
};

/**
 * Get running time entry for a user
 */
export const getRunningTimeEntry = async (userId) => {
  const [running] = await db
    .select()
    .from(timeEntries)
    .where(and(eq(timeEntries.userId, userId), eq(timeEntries.isRunning, true)))
    .limit(1);

  return running || null;
};

/**
 * Get all running time entries (for dashboard)
 */
export const getAllRunningTimeEntries = async () => {
  const result = await db
    .select({
      id: timeEntries.id,
      userId: timeEntries.userId,
      startTime: timeEntries.startTime,
      pausedAt: timeEntries.pausedAt,
      pausedDuration: timeEntries.pausedDuration,
      firstName: users.firstName,
      lastName: users.lastName,
    })
    .from(timeEntries)
    .innerJoin(users, eq(timeEntries.userId, users.id))
    .where(eq(timeEntries.isRunning, true));

  return result;
};

/**
 * Get time entry by ID
 */
export const getTimeEntryById = async (entryId) => {
  const [entry] = await db
    .select()
    .from(timeEntries)
    .where(eq(timeEntries.id, entryId))
    .limit(1);

  if (!entry) {
    throw new NotFoundError('Záznam nenájdený');
  }

  return entry;
};

/**
 * Get all time entries for a user with optional filters
 */
export const getAllTimeEntries = async (userId, filters = {}) => {
  const { projectId, todoId, companyId, startDate, endDate } = filters;

  let query = db.select().from(timeEntries).where(eq(timeEntries.userId, userId));

  const conditions = [eq(timeEntries.userId, userId)];

  if (projectId) {
    conditions.push(eq(timeEntries.projectId, projectId));
  }

  if (todoId) {
    conditions.push(eq(timeEntries.todoId, todoId));
  }

  if (companyId) {
    conditions.push(eq(timeEntries.companyId, companyId));
  }

  if (startDate) {
    conditions.push(gte(timeEntries.startTime, new Date(startDate)));
  }

  if (endDate) {
    conditions.push(lte(timeEntries.startTime, new Date(endDate)));
  }

  if (conditions.length > 0) {
    query = query.where(and(...conditions));
  }

  const result = await query.orderBy(desc(timeEntries.startTime));
  return result;
};

/**
 * Get time entries for a specific month
 */
export const getMonthlyTimeEntries = async (userId, year, month) => {
  const startDate = new Date(year, month - 1, 1);
  const endDate = new Date(year, month, 0, 23, 59, 59, 999);

  return await db
    .select()
    .from(timeEntries)
    .where(
      and(
        eq(timeEntries.userId, userId),
        gte(timeEntries.startTime, startDate),
        lte(timeEntries.startTime, endDate)
      )
    )
    .orderBy(desc(timeEntries.startTime));
};

/**
 * Generate an XLSX timesheet for a given month and user.
 */
export const generateMonthlyTimesheet = async (userId, year, month) => {
  const startDate = new Date(year, month - 1, 1);
  const endDate = new Date(year, month, 0, 23, 59, 59, 999);

  const [user] = await db
    .select({ username: users.username, firstName: users.firstName, lastName: users.lastName })
    .from(users)
    .where(eq(users.id, userId))
    .limit(1);

  if (!user) throw new NotFoundError('Používateľ nenájdený');

  const entries = await db
    .select({
      id: timeEntries.id,
      startTime: timeEntries.startTime,
      endTime: timeEntries.endTime,
      duration: timeEntries.duration,
      description: timeEntries.description,
      projectName: projects.name,
      todoTitle: todos.title,
      companyName: companies.name,
    })
    .from(timeEntries)
    .leftJoin(projects, eq(timeEntries.projectId, projects.id))
    .leftJoin(todos, eq(timeEntries.todoId, todos.id))
    .leftJoin(companies, eq(timeEntries.companyId, companies.id))
    .where(
      and(
        eq(timeEntries.userId, userId),
        gte(timeEntries.startTime, startDate),
        lte(timeEntries.startTime, endDate)
      )
    )
    .orderBy(timeEntries.startTime);

  const completedEntries = entries.filter((entry) => entry.endTime && entry.duration !== null);
  if (completedEntries.length === 0) {
    throw new NotFoundError('Žiadne dokončené záznamy pre daný mesiac');
  }

  const { totalMinutes, dailyTotals } = computeDailyTotals(completedEntries);

  const fullName = [user.firstName, user.lastName].filter(Boolean).join(' ') || user.username;
  const periodLabel = `${year}-${String(month).padStart(2, '0')}`;

  const columns = [
    { key: 'date', header: 'Date', width: 12 },
    { key: 'project', header: 'Project', width: 28 },
    { key: 'todo', header: 'Todo', width: 28 },
    { key: 'company', header: 'Company', width: 24 },
    { key: 'description', header: 'Description', width: 40 },
    { key: 'start', header: 'Start', width: 12 },
    { key: 'end', header: 'End', width: 12 },
    { key: 'duration', header: 'Duration (h:mm)', width: 16 },
  ];

  const { workbook, worksheet } = generateTimesheetWorkbook({
    title: 'Timesheet',
    headerMeta: [
      `Name: ${fullName}`,
      `Period: ${periodLabel}`,
      `Generated: ${new Date().toLocaleString()}`,
    ],
    columns,
    headerRowNumber: 6,
    entries: completedEntries,
    formatRow: (entry) => ({
      date: formatDate(entry.startTime),
      project: entry.projectName || '',
      todo: entry.todoTitle || '',
      company: entry.companyName || '',
      description: entry.description || '',
      start: formatTime(entry.startTime),
      end: formatTime(entry.endTime),
      duration: formatDuration(entry.duration),
    }),
  });

  addDailySummary(worksheet, dailyTotals, totalMinutes);

  const namePrefix = getUserNamePrefix(user);
  const filename = `${namePrefix}-vykazprace-${periodLabel}.xlsx`;

  const newTimesheet = await saveTimesheetFile(workbook, { userId, year, month, filename });

  return {
    timesheet: {
      id: newTimesheet.id,
      fileName: newTimesheet.fileName,
      fileType: newTimesheet.fileType,
      fileSize: newTimesheet.fileSize,
      year: newTimesheet.year,
      month: newTimesheet.month,
      isGenerated: newTimesheet.isGenerated,
      uploadedAt: newTimesheet.uploadedAt,
    },
    totals: {
      totalMinutes,
      totalFormatted: formatDuration(totalMinutes),
      daily: dailyTotals,
    },
  };
};

/**
 * Update time entry
 */
export const updateTimeEntry = async (entryId, actor, data, auditContext = null) => {
  const { userId, role } = actor;
  const entry = await getTimeEntryById(entryId);

  const hasFullAccess = role === 'admin' || role === 'team_leader';
  if (entry.userId !== userId && !hasFullAccess) {
    throw new ForbiddenError('Nemáte oprávnenie upraviť tento záznam');
  }

  if (entry.isRunning) {
    throw new BadRequestError('Nemôžete upraviť bežiaci časovač. Najprv ho zastavte.');
  }

  const { startTime, endTime } = data;
  const projectId = normalizeOptionalId(data.projectId);
  const todoId = normalizeOptionalId(data.todoId);
  const companyId = normalizeOptionalId(data.companyId);
  const description = normalizeOptionalText(data.description);

  await validateRelatedEntities(projectId, todoId, companyId);

  // Calculate new duration if times are changed
  let newDuration = entry.duration;
  const newStartTime = startTime ? new Date(startTime) : new Date(entry.startTime);
  const newEndTime = endTime ? new Date(endTime) : (entry.endTime ? new Date(entry.endTime) : null);

  if (newEndTime) {
    if (newEndTime <= newStartTime) {
      throw new BadRequestError('Čas ukončenia musí byť po čase začiatku');
    }
    const pausedSeconds = entry.pausedDuration || 0;
    newDuration = Math.max(0, Math.round(((newEndTime - newStartTime) / 60000) - (pausedSeconds / 60)));
  }

  const [updated] = await db
    .update(timeEntries)
    .set({
      startTime: newStartTime,
      endTime: newEndTime,
      duration: newDuration,
      projectId: projectId !== undefined ? projectId : entry.projectId,
      todoId: todoId !== undefined ? todoId : entry.todoId,
      companyId: companyId !== undefined ? companyId : entry.companyId,
      description: description !== undefined ? description : entry.description,
      isEdited: true,
      updatedAt: new Date(),
    })
    .where(eq(timeEntries.id, entryId))
    .returning();

  if (auditContext) {
    await logTimeEntryUpdated(auditContext.userId, entryId, { description: entry.description, duration: entry.duration }, { description: updated.description, duration: updated.duration }, auditContext.ipAddress, auditContext.userAgent);
  }

  return updated;
};

/**
 * Delete time entry
 */
export const deleteTimeEntry = async (entryId, actor, auditContext = null) => {
  const { userId, role } = actor;
  const entry = await getTimeEntryById(entryId);

  const hasFullAccess = role === 'admin' || role === 'team_leader';
  if (entry.userId !== userId && !hasFullAccess) {
    throw new ForbiddenError('Nemáte oprávnenie odstrániť tento záznam');
  }

  if (entry.isRunning) {
    throw new BadRequestError('Nemôžete odstrániť bežiaci časovač. Najprv ho zastavte.');
  }

  await db.delete(timeEntries).where(eq(timeEntries.id, entryId));

  if (auditContext) {
    await logTimeEntryDeleted(auditContext.userId, entryId, entry.description || 'Time entry', entry.duration, auditContext.ipAddress, auditContext.userAgent);
  }

  return { success: true, message: 'Záznam bol odstránený' };
};

/**
 * Get time entry with related data (project, todo, company)
 */
export const getTimeEntryWithRelations = async (entryId) => {
  const entry = await getTimeEntryById(entryId);

  let project = null;
  if (entry.projectId) {
    [project] = await db
      .select()
      .from(projects)
      .where(eq(projects.id, entry.projectId))
      .limit(1);
  }

  let todo = null;
  if (entry.todoId) {
    [todo] = await db
      .select()
      .from(todos)
      .where(eq(todos.id, entry.todoId))
      .limit(1);
  }

  let company = null;
  if (entry.companyId) {
    [company] = await db
      .select()
      .from(companies)
      .where(eq(companies.id, entry.companyId))
      .limit(1);
  }

  return {
    ...entry,
    project,
    todo,
    company,
  };
};

/**
 * Generate an XLSX timesheet for a specific company for a given month and user.
 */
export const generateCompanyTimesheet = async (userId, year, month, companyId) => {
  const startDate = new Date(year, month - 1, 1);
  const endDate = new Date(year, month, 0, 23, 59, 59, 999);

  const [user] = await db
    .select({ username: users.username, firstName: users.firstName, lastName: users.lastName })
    .from(users)
    .where(eq(users.id, userId))
    .limit(1);

  if (!user) throw new NotFoundError('Používateľ nenájdený');

  const company = await validateCompanyExists(companyId);

  const entries = await db
    .select({
      id: timeEntries.id,
      startTime: timeEntries.startTime,
      endTime: timeEntries.endTime,
      duration: timeEntries.duration,
      description: timeEntries.description,
      projectName: projects.name,
      todoTitle: todos.title,
    })
    .from(timeEntries)
    .leftJoin(projects, eq(timeEntries.projectId, projects.id))
    .leftJoin(todos, eq(timeEntries.todoId, todos.id))
    .where(
      and(
        eq(timeEntries.userId, userId),
        eq(timeEntries.companyId, companyId),
        gte(timeEntries.startTime, startDate),
        lte(timeEntries.startTime, endDate)
      )
    )
    .orderBy(timeEntries.startTime);

  const completedEntries = entries.filter((entry) => entry.endTime && entry.duration !== null);
  if (completedEntries.length === 0) {
    throw new NotFoundError('Žiadne dokončené záznamy pre danú firmu a mesiac');
  }

  const { totalMinutes, dailyTotals } = computeDailyTotals(completedEntries);

  const fullName = [user.firstName, user.lastName].filter(Boolean).join(' ') || user.username;
  const periodLabel = `${year}-${String(month).padStart(2, '0')}`;

  const columns = [
    { key: 'date', header: 'Date', width: 12 },
    { key: 'project', header: 'Project', width: 28 },
    { key: 'todo', header: 'Todo', width: 28 },
    { key: 'description', header: 'Description', width: 40 },
    { key: 'start', header: 'Start', width: 12 },
    { key: 'end', header: 'End', width: 12 },
    { key: 'duration', header: 'Duration (h:mm)', width: 16 },
  ];

  const { workbook, worksheet } = generateTimesheetWorkbook({
    title: 'Company Timesheet',
    headerMeta: [
      `Company: ${company.name}`,
      `Employee: ${fullName}`,
      `Period: ${periodLabel}`,
      `Generated: ${new Date().toLocaleString()}`,
    ],
    columns,
    headerRowNumber: 7,
    entries: completedEntries,
    formatRow: (entry) => ({
      date: formatDate(entry.startTime),
      project: entry.projectName || '',
      todo: entry.todoTitle || '',
      description: entry.description || '',
      start: formatTime(entry.startTime),
      end: formatTime(entry.endTime),
      duration: formatDuration(entry.duration),
    }),
  });

  addDailySummary(worksheet, dailyTotals, totalMinutes);

  const namePrefix = getUserNamePrefix(user);
  const companySlug = company.name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
  const filename = `${namePrefix}-vykazprace-${companySlug}-${periodLabel}.xlsx`;

  const newTimesheet = await saveTimesheetFile(workbook, { userId, year, month, filename });

  return {
    timesheet: {
      id: newTimesheet.id,
      fileName: newTimesheet.fileName,
      fileType: newTimesheet.fileType,
      fileSize: newTimesheet.fileSize,
      year: newTimesheet.year,
      month: newTimesheet.month,
      isGenerated: newTimesheet.isGenerated,
      uploadedAt: newTimesheet.uploadedAt,
    },
    companyName: company.name,
    totals: {
      totalMinutes,
      totalFormatted: formatDuration(totalMinutes),
      daily: dailyTotals,
    },
  };
};

/**
 * Get monthly statistics for a user
 */
export const getMonthlyStats = async (userId, year, month) => {
  const entries = await getMonthlyTimeEntries(userId, year, month);

  const totalMinutes = entries.reduce((sum, entry) => {
    return sum + (entry.duration || 0);
  }, 0);

  const uniqueDays = new Set(
    entries
      .filter((e) => !e.isRunning)
      .map((e) => new Date(e.startTime).toDateString())
  ).size;

  const byProject = {};
  entries.forEach((entry) => {
    if (entry.projectId && entry.duration) {
      if (!byProject[entry.projectId]) {
        byProject[entry.projectId] = 0;
      }
      byProject[entry.projectId] += entry.duration;
    }
  });

  const byCompany = {};
  entries.forEach((entry) => {
    if (entry.companyId && entry.duration) {
      if (!byCompany[entry.companyId]) {
        byCompany[entry.companyId] = 0;
      }
      byCompany[entry.companyId] += entry.duration;
    }
  });

  return {
    totalMinutes,
    totalHours: Math.floor(totalMinutes / 60),
    remainingMinutes: totalMinutes % 60,
    daysWorked: uniqueDays,
    averagePerDay: uniqueDays > 0 ? Math.round(totalMinutes / uniqueDays) : 0,
    entriesCount: entries.length,
    byProject,
    byCompany,
  };
};
