import { z } from 'zod';
import { IFile } from '../../config/types/interface';
import {
  dateTransformSchema,
  defaultTransformSchema,
  imageSchema,
} from '../shared/shared.dto';

const optionalString = z.preprocess((value) => {
  if (typeof value === 'string' && value.trim() === '') return undefined;
  return value;
}, z.string().trim().optional());

const optionalUuid = z.preprocess((value) => {
  if (typeof value === 'string' && value.trim() === '') return undefined;
  return value;
}, z.string().uuid().optional());

const optionalDate = z.preprocess((value) => {
  if (value === null || value === undefined || value === '') return undefined;
  return value;
}, z.coerce.date().optional());

const leaveStatusSchema = z.enum([
  'pending',
  'approved',
  'rejected',
  'cancelled',
]);

const approverTypeSchema = z.enum(['owner', 'member']);

const memberPreviewSchema = z.object({
  id: z.string().uuid(),
  employee_id: z.string(),
  first_name: z.string(),
  last_name: defaultTransformSchema,
  nickname: defaultTransformSchema,
  image: imageSchema.nullable().optional(),
  role: z.enum(['owner', 'admin', 'member']).optional(),
  department: z
    .object({
      id: z.string().uuid(),
      name: z.string(),
    })
    .nullable()
    .optional(),
});

const optionalFileSchema = z
  .any()
  .transform((value) => {
    if (
      !value ||
      typeof value !== 'object' ||
      !('file_name' in value) ||
      typeof value.file_name !== 'string' ||
      value.file_name.trim() === ''
    ) {
      return undefined;
    }

    const host = process.env.SERVER_HOST ?? '';
    const hasHost = host.length > 0 && value.file_name.startsWith(host);

    return {
      ...value,
      file_name: hasHost ? value.file_name : `${host}/${value.file_name}`,
    };
  })
  .optional();

export const createLeaveSchema = z
  .object({
    member_id: z.string().uuid({ message: 'Member ID is required' }),
    start_date: z.coerce.date({ message: 'Start date is required' }),
    end_date: z.coerce.date({ message: 'End date is required' }),
    approver_id: optionalUuid,
    approver_type: approverTypeSchema.optional(),
    leave_type: z
      .enum(
        [
          'annual',
          'sick',
          'casual',
          'maternity',
          'paternity',
          'personal',
          'compensatory',
          'unpaid',
          'day_off',
        ],
        {
          message: 'Invalid leave type',
        },
      )
      .optional(),
    reason: optionalString,
    status: leaveStatusSchema.optional(),
    document: optionalFileSchema,
  })
  .superRefine((data, ctx) => {
    if (data.end_date < data.start_date) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ['end_date'],
        message: 'End date must be after or equal to start date',
      });
    }
  });

export const updateLeaveSchema = z
  .object({
    member_id: optionalUuid,
    start_date: optionalDate,
    end_date: optionalDate,
    reason: optionalString,
    approver_id: optionalUuid,
    approver_type: approverTypeSchema.optional(),
    status: leaveStatusSchema.optional(),
    document: optionalFileSchema.nullable(),
  })
  .refine((data) => Object.keys(data).length > 0, {
    message: 'At least one field must be provided',
  })
  .superRefine((data, ctx) => {
    if (data.start_date && data.end_date && data.end_date < data.start_date) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ['end_date'],
        message: 'End date must be after or equal to start date',
      });
    }
  });

export const updateLeaveStatusSchema = z
  .object({
    status: leaveStatusSchema,
    approver_type: approverTypeSchema.optional(),
    approver_id: optionalUuid,
  })
  .superRefine((data, ctx) => {
    if (data.status === 'approved' || data.status === 'rejected') {
      if (!data.approver_type) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ['approver_type'],
          message:
            'Approver type is required when approving or rejecting leave',
        });
      }

      if (!data.approver_id) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ['approver_id'],
          message: 'Approver ID is required when approving or rejecting leave',
        });
      }
    }
  });

export const leaveFilterSchema = z
  .object({
    member_id: optionalUuid,
    memberId: optionalUuid,
    status: leaveStatusSchema.optional(),
    start_date: optionalString, // Accept date as string in query params
    end_date: optionalString,
    department_id: optionalUuid,
    data_scope: z.enum(['own', 'all']).optional(),
    page: z.preprocess((value) => {
      if (value === undefined || value === null || value === '') return 1;
      const parsed = Number(value);
      return Number.isNaN(parsed) ? value : parsed;
    }, z.number().int().min(1).default(1)),
    limit: z.preprocess((value) => {
      if (value === undefined || value === null || value === '') return 10;
      const parsed = Number(value);
      return Number.isNaN(parsed) ? value : parsed;
    }, z.number().int().min(1).max(100).default(10)),
  })
  .transform(({ memberId, ...rest }) => ({
    ...rest,
    member_id: rest.member_id ?? memberId,
  }));

export const leaveResponseSchema = z.object({
  id: z.string().uuid().optional(),
  member_id: z.string().uuid().optional(),
  member: memberPreviewSchema.optional().nullable(),
  start_date: z
    .date()
    .transform((date) => date.toISOString())
    .optional(),
  end_date: z
    .date()
    .transform((date) => date.toISOString())
    .optional(),
  reason: defaultTransformSchema,
  document: z
    .any()
    .transform((val: IFile) => {
      if (!val || !val?.file_name) return null;
      const isHostIncluded = val?.file_name?.includes(process.env.SERVER_HOST);
      return {
        ...val,
        file_name: isHostIncluded
          ? val?.file_name
          : `${process.env.SERVER_HOST}/${val?.file_name}`,
      };
    })
    .nullable()
    .optional(),
  status: leaveStatusSchema.optional(),
  leave_type: optionalString,
  approver_type: approverTypeSchema.optional().nullable(),
  approver_owner_id: z.string().uuid().optional().nullable(),
  approver_member_id: z.string().uuid().optional().nullable(),
  approver_owner: z
    .object({
      id: z.string().uuid(),
      name: z.string(),
      email: z.string().email(),
      image: imageSchema.nullable().optional(),
    })
    .optional()
    .nullable(),
  approver_member: memberPreviewSchema.optional().nullable(),
  approved_at: z
    .date()
    .transform((date) => date.toISOString())
    .optional()
    .nullable(),
  created_at: dateTransformSchema,
  updated_at: dateTransformSchema,
});

export const leaveBalanceResponseSchema = z.object({
  id: z.string().uuid(),
  member_id: z.string().uuid(),
  first_name: z.string().optional(),
  last_name: z.string().optional(),
  image: imageSchema,
  department_id: z.string().uuid().optional(),
  department_name: z.string().optional(),
  used_leaves: z.number().optional(),
  remaining_leaves: z.number().optional(),
  total_leaves: z.number().optional(),
});

export const bulkUpdateLeaveStatusSchema = z.object({
  leave_ids: z.array(z.string().uuid()).min(1, {
    message: 'At least one leave ID must be provided',
  }),
  status: leaveStatusSchema,
  approver_type: approverTypeSchema.optional(),
  approver_id: optionalUuid,
});

export const leavesResponseSchema = z.array(leaveResponseSchema);
export const leaveBalanceResponseArraySchema = z.array(
  leaveBalanceResponseSchema,
);

export type createLeaveDto = z.infer<typeof createLeaveSchema>;
export type updateLeaveDto = z.infer<typeof updateLeaveSchema>;
export type updateLeaveStatusDto = z.infer<typeof updateLeaveStatusSchema>;
export type leaveFilterDto = z.infer<typeof leaveFilterSchema>;
export type leaveResponseDto = z.infer<typeof leaveResponseSchema>;
export type leavesResponseDto = z.infer<typeof leavesResponseSchema>;
export type bulkUpdateLeaveStatusDto = z.infer<
  typeof bulkUpdateLeaveStatusSchema
>;
export type leaveBalanceResponseDto = z.infer<
  typeof leaveBalanceResponseSchema
>;
export type leaveBalanceResponseArrayDto = z.infer<
  typeof leaveBalanceResponseArraySchema
>;
