'use server'

import { Knex } from 'knex'; // Import Knex type
import { createTenantKnex } from '@alga-psa/db';
import {
  ITimeSheet,
  ITimeSheetView,
  ITimePeriodWithStatusView,
  TimeSheetStatus
} from '@alga-psa/types';
import { toPlainDate } from '@alga-psa/core';
import { validateData } from '@alga-psa/validation';
import {
  submitTimeSheetParamsSchema,
  SubmitTimeSheetParams,
  fetchTimePeriodsParamsSchema,
  FetchTimePeriodsParams,
  fetchOrCreateTimeSheetParamsSchema,
  FetchOrCreateTimeSheetParams
} from './timeEntrySchemas'; // Import schemas from the new module
import { withAuth, hasPermission } from '@alga-psa/auth';
import { assertCanActOnBehalf } from './timeEntryDelegationAuth';

function captureAnalytics(_event: string, _properties?: Record<string, any>, _userId?: string): void {
  // Intentionally no-op: avoid pulling analytics (and its tenancy/client-portal deps) into scheduling.
}

// Type for Knex raw query results with aggregate functions
interface TimeEntriesInfo {
  entry_count: number | string;
  total_hours: number | string | null;
}

interface TimePeriodSummaryRow {
  hours_entered: number | string | null;
  days_logged: number | string | null;
  last_entry_date?: string | Date | null;
}

function parseNumericValue(value: number | string | null | undefined): number {
  if (value == null) {
    return 0;
  }

  const parsed = typeof value === 'number' ? value : Number.parseFloat(value);
  return Number.isFinite(parsed) ? parsed : 0;
}

function toDateOnlyString(value: string | Date | null | undefined): string | undefined {
  if (!value) {
    return undefined;
  }

  if (value instanceof Date) {
    return value.toISOString().slice(0, 10);
  }

  return value.slice(0, 10);
}

export const fetchTimeSheets = withAuth(async (user, { tenant }): Promise<ITimeSheet[]> => {
  const currentUserId = user.user_id;

  console.log('Fetching time sheets for user:', currentUserId);

  const {knex: db} = await createTenantKnex();
  const query = db('time_sheets')
    .join('time_periods', function() {
      this.on('time_sheets.period_id', '=', 'time_periods.period_id')
          .andOn('time_sheets.tenant', '=', 'time_periods.tenant');
    })
    .where({
      'time_sheets.user_id': currentUserId,
      'time_sheets.tenant': tenant
    })
    .orderBy('time_periods.start_date', 'desc')
    .select(
      'time_sheets.*',
      'time_periods.start_date',
      'time_periods.end_date'
    );

  console.log('SQL Query:', query.toString());

  const timeSheets = await query;

  return timeSheets.map((sheet): ITimeSheet => ({
    ...sheet,
    time_period: {
      period_id: sheet.period_id,
      start_date: toPlainDate(sheet.start_date).toString(),
      end_date: toPlainDate(sheet.end_date).toString(),
      tenant: sheet.tenant
    }
  }));
});

export const submitTimeSheet = withAuth(async (user, { tenant }, timeSheetId: string): Promise<ITimeSheet> => {
  // Validate input
  const validatedParams = validateData<SubmitTimeSheetParams>(submitTimeSheetParamsSchema, { timeSheetId });

  const {knex: db} = await createTenantKnex();

  try {
    if (!await hasPermission(user, 'timesheet', 'submit', db)) {
      throw new Error('Permission denied: Cannot submit timesheets');
    }

    return await db.transaction(async (trx) => {
      // Get timesheet info for analytics
      const timeSheetInfo = await trx('time_sheets')
        .where({
          id: validatedParams.timeSheetId,
          tenant
        })
        .first();

      if (!timeSheetInfo) {
        throw new Error('Time sheet not found');
      }

      await assertCanActOnBehalf(user, tenant, timeSheetInfo.user_id, trx);

      // Get entry count and total hours for analytics
      const entriesInfo = await trx('time_entries')
        .where({
          time_sheet_id: validatedParams.timeSheetId,
          tenant
        })
        .select(
          trx.raw('COUNT(*) as entry_count'),
          trx.raw('SUM(billable_duration) / 60 as total_hours')
        )
        .first() as unknown as TimeEntriesInfo | undefined;

      // Get period info
      const periodInfo = await trx('time_periods')
        .where({
          period_id: timeSheetInfo.period_id,
          tenant
        })
        .first();

      // Update the time sheet status
      const [updatedTimeSheet] = await trx('time_sheets')
        .where({
          id: validatedParams.timeSheetId,
          tenant
        })
        .update({
          approval_status: 'SUBMITTED',
          submitted_at: trx.fn.now()
        })
        .returning('*');

      // Update all time entries associated with this time sheet
      await trx('time_entries')
        .where({
          time_sheet_id: validatedParams.timeSheetId,
          tenant
        })
        .update({
          approval_status: 'SUBMITTED',
          updated_at: trx.fn.now()
        });

      // Track analytics
      captureAnalytics('time_sheet_submitted', {
        time_sheet_id: validatedParams.timeSheetId,
        entry_count: entriesInfo?.entry_count || 0,
        total_hours: parseFloat(String(entriesInfo?.total_hours ?? '0')),
        period_start: periodInfo?.start_date,
        period_end: periodInfo?.end_date
      }, user.user_id);

      return updatedTimeSheet;
    });
  } catch (error) {
    console.error('Error submitting time sheet:', error);
    throw new Error('Failed to submit time sheet');
  }
});

export const fetchAllTimeSheets = withAuth(async (_user, { tenant }): Promise<ITimeSheet[]> => {
  const {knex: db} = await createTenantKnex();

  console.log('Fetching all time sheets');

  const query = db('time_sheets')
    .join('time_periods', function() {
      this.on('time_sheets.period_id', '=', 'time_periods.period_id')
          .andOn('time_sheets.tenant', '=', 'time_periods.tenant');
    })
    .where('time_sheets.tenant', tenant)
    .orderBy('time_periods.start_date', 'desc')
    .select(
      'time_sheets.*',
      'time_periods.start_date',
      'time_periods.end_date'
    );

  console.log('SQL Query:', query.toString());

  const timeSheets = await query;

  return timeSheets.map((sheet): ITimeSheet => ({
    ...sheet,
    time_period: {
      start_date: toPlainDate(sheet.start_date).toString(),
      end_date: toPlainDate(sheet.end_date).toString()
    }
  }));
});

export const fetchTimePeriods = withAuth(async (user, { tenant }, userId: string): Promise<ITimePeriodWithStatusView[]> => {
  // Validate input
  const validatedParams = validateData<FetchTimePeriodsParams>(fetchTimePeriodsParamsSchema, { userId });

  const {knex: db} = await createTenantKnex();

  await assertCanActOnBehalf(user, tenant, validatedParams.userId, db);

  const timeEntrySummaries = db('time_sheets as summary_ts')
    .join('time_periods as summary_tp', function() {
      this.on('summary_ts.period_id', '=', 'summary_tp.period_id')
          .andOn('summary_ts.tenant', '=', 'summary_tp.tenant');
    })
    .leftJoin('time_entries as te', function() {
      this.on('summary_ts.id', '=', 'te.time_sheet_id')
          .andOn('summary_ts.tenant', '=', 'te.tenant')
          .andOn(db.raw('te.work_date >= summary_tp.start_date'))
          .andOn(db.raw('te.work_date < summary_tp.end_date'));
    })
    .where({
      'summary_ts.tenant': tenant,
      'summary_ts.user_id': validatedParams.userId
    })
    .groupBy('summary_ts.period_id', 'summary_ts.tenant')
    .select(
      'summary_ts.period_id',
      'summary_ts.tenant',
      db.raw('COALESCE(SUM(EXTRACT(EPOCH FROM (te.end_time - te.start_time)) / 3600.0), 0) as hours_entered'),
      db.raw('COUNT(DISTINCT te.work_date) as days_logged'),
      db.raw('MAX(te.work_date) as last_entry_date')
    )
    .as('tes');

  const periods = await db('time_periods as tp')
    .leftJoin('time_sheets as ts', function() {
      this.on('tp.period_id', '=', 'ts.period_id')
          .andOn('tp.tenant', '=', 'ts.tenant')
          .andOn('ts.user_id', '=', db.raw('?', [validatedParams.userId]));
    })
    .leftJoin(timeEntrySummaries, function() {
      this.on('tp.period_id', '=', 'tes.period_id')
          .andOn('tp.tenant', '=', 'tes.tenant');
    })
    .where({ 'tp.tenant': tenant })
    .orderBy('tp.start_date', 'desc')
    .select(
      'tp.*',
      'ts.approval_status',
      db.raw('COALESCE(ts.approval_status, ?) as timeSheetStatus', ['DRAFT']),
      'tes.hours_entered',
      'tes.days_logged',
      'tes.last_entry_date'
    );

  console.log('Fetched periods:', periods);

  return periods.map((period): ITimePeriodWithStatusView => {
    const summary = period as typeof period & TimePeriodSummaryRow;

    return {
      ...period,
      start_date: toPlainDate(period.start_date).toString(),
      end_date: toPlainDate(period.end_date).toString(),
      timeSheetStatus: (period.approval_status || period.timeSheetStatus || 'DRAFT') as TimeSheetStatus,
      hoursEntered: parseNumericValue(summary.hours_entered),
      daysLogged: parseNumericValue(summary.days_logged),
      lastEntryDate: toDateOnlyString(summary.last_entry_date)
    };
  });
});

export const fetchOrCreateTimeSheet = withAuth(async (user, { tenant }, userId: string, periodId: string): Promise<ITimeSheetView> => {
  // Validate input
  const validatedParams = validateData<FetchOrCreateTimeSheetParams>(
    fetchOrCreateTimeSheetParamsSchema,
    { userId, periodId }
  );

  const {knex: db} = await createTenantKnex();

  await assertCanActOnBehalf(user, tenant, validatedParams.userId, db);

  let timeSheet = await db('time_sheets')
    .where({
      user_id: validatedParams.userId,
      period_id: validatedParams.periodId,
      tenant
    })
    .first();

  if (!timeSheet) {
    [timeSheet] = await db('time_sheets')
      .insert({
        user_id: validatedParams.userId,
        period_id: validatedParams.periodId,
        approval_status: 'DRAFT',
        tenant
      })
      .returning('*');
  }

  const timePeriod = await db('time_periods')
    .where({
      period_id: validatedParams.periodId,
      tenant
    })
    .first();

  // Fetch comments for the time sheet
  const comments = await db('time_sheet_comments')
    .where({
      time_sheet_id: timeSheet.id,
      tenant
    })
    .orderBy('created_at', 'desc')
    .select('*');

  return {
    ...timeSheet,
    time_period: {
      ...timePeriod,
      start_date: toPlainDate(timePeriod.start_date).toString(),
      end_date: toPlainDate(timePeriod.end_date).toString()
    },
    comments: comments,
  };
});
