import { HttpStatus, Injectable } from '@nestjs/common';
import { OrgTimezoneService } from '../../common/timezone/org-timezone.service';
import { assertSettingsModuleAccess } from '../../common/access-control/permission-resolver.helpers';
import { PermissionUserContext } from '../../common/access-control/permission-context.types';
import { PermissionResolverService } from '../../common/access-control/permission-resolver.service';
import {
  DatabaseService,
  envConfig,
  ErrorResponse,
  logger,
  UploadService,
} from '../../config';
import { createGeneralError } from '../../utils';
import {
  CompanyInfoResponseDto,
  companyInfoResponseSchema,
  LeavePolicyResponseDto,
  leavePolicyResponseSchema,
  UpdateCompanyInfoDto,
  UpdateLeavePolicyDto,
  UpdateWorkScheduleDto,
  WorkScheduleResponseDto,
  workScheduleResponseSchema,
  UpdateAttendanceValidationSettingsDto,
  AttendanceValidationSettingsResponseDto,
  attendanceValidationSettingsResponseSchema,
} from './configurations.dto';

@Injectable()
export class ConfigurationsService {
  constructor(
    private readonly db: DatabaseService,
    private readonly uploadService: UploadService,
    private readonly permissionResolver: PermissionResolverService,
    private readonly orgTimezone: OrgTimezoneService,
  ) {}

  private async assertSettingsAccess(
    ctx: PermissionUserContext,
    manage = false,
  ) {
    const permissions = await this.permissionResolver.resolveForUser(ctx);
    assertSettingsModuleAccess(permissions, manage);
  }

  async createDefaultConfiguration(owner_id: string): Promise<void> {
    const { profile } = envConfig().server;
    await Promise.all([
      this.db.working_schedule.create({
        data: {
          working_days: [
            'saturday',
            'sunday',
            'monday',
            'tuesday',
            'wednesday',
            'thursday',
          ],
          working_days_per_week: 6,
          working_hours_per_day: 8,
          timezone: 'Asia/Dhaka',
          monthly_correction_limit: 2,
          owner_id,
        },
      }),
      this.db.leave_policy.create({
        data: {
          annual_leave_days: 10,
          sick_leave_days: 10,
          owner_id,
        },
      }),
      this.db.company.create({
        data: {
          name: 'NT/APL',
          company_code: 'NT/APL',
          email: 'NT/APL',
          phone: 'NT/APL',
          address: 'NT/APL',
          website: 'NT/APL',
          logo: {
            file_name: profile,
            original_name: 'profile.webp',
            file_size: 8192,
            mime_type: 'image/webp',
          },
          description: 'NT/APL',
          founded_at: new Date(),
          industry: 'NT/APL',
          owner_id,
        },
      }),
    ]);
  }

  async getCompanyInfo(
    owner_id: string,
    ctx: PermissionUserContext,
  ): Promise<{
    data: CompanyInfoResponseDto;
    message: string;
    status: HttpStatus;
    details: null;
  }> {
    try {
      await this.assertSettingsAccess(ctx);
      const company = await this.db.company.findFirst({ where: { owner_id } });
      if (!company) {
        throw createGeneralError(
          'Company information not found',
          'company',
          HttpStatus.NOT_FOUND,
        );
      }
      const parsedResult = companyInfoResponseSchema.safeParse(company);
      if (!parsedResult.success) {
        throw createGeneralError(
          parsedResult.error.message,
          'root',
          HttpStatus.BAD_REQUEST,
        );
      }
      const parsedData = parsedResult.data;
      return {
        data: parsedData,
        message: 'Company information retrieved successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in getCompanyInfo: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async getWorkSchedule(
    owner_id: string,
    ctx: PermissionUserContext,
  ): Promise<{
    data: WorkScheduleResponseDto;
    message: string;
    status: HttpStatus;
    details: null;
  }> {
    try {
      const schedule = await this.db.working_schedule.findFirst({
        where: { owner_id },
      });
      if (!schedule) {
        throw createGeneralError(
          'Work schedule not found',
          'working_schedule',
          HttpStatus.NOT_FOUND,
        );
      }
      const parsedResult = workScheduleResponseSchema.safeParse(schedule);
      if (!parsedResult.success) {
        throw createGeneralError(
          parsedResult.error.message,
          'working_schedule',
          HttpStatus.BAD_REQUEST,
        );
      }
      const parsedData = parsedResult.data;
      return {
        data: parsedData,
        message: 'Work schedule retrieved successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in getWorkSchedule: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async getLeavePolicy(
    owner_id: string,
    ctx: PermissionUserContext,
  ): Promise<{
    data: LeavePolicyResponseDto;
    message: string;
    status: HttpStatus;
    details: null;
  }> {
    try {
      await this.assertSettingsAccess(ctx);
      const schedule = await this.db.leave_policy.findFirst({
        where: { owner_id },
      });
      if (!schedule) {
        throw createGeneralError(
          'Leave policy not found',
          'leave_policy',
          HttpStatus.NOT_FOUND,
        );
      }
      const parsedResult = leavePolicyResponseSchema.safeParse(schedule);
      if (!parsedResult.success) {
        throw createGeneralError(
          parsedResult.error.message,
          'leave_policy',
          HttpStatus.BAD_REQUEST,
        );
      }
      const parsedData = parsedResult.data;
      return {
        data: parsedData,
        message: 'Leave policy retrieved successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in getLeavePolicy: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async updateCompanyInfo(
    id: string,
    owner_id: string,
    data: UpdateCompanyInfoDto,
    ctx: PermissionUserContext,
  ): Promise<{
    data: CompanyInfoResponseDto;
    message: string;
    status: HttpStatus;
    details: null;
  }> {
    try {
      await this.assertSettingsAccess(ctx, true);
      const company = await this.db.company.findFirst({
        where: { id, owner_id },
      });
      if (!company) {
        throw createGeneralError(
          'Company information not found',
          'root',
          HttpStatus.NOT_FOUND,
        );
      }
      const { profile } = envConfig().server;
      const shouldClearImage = data?.logo === null;
      let image = data?.logo;
      if (image) {
        image = await this.uploadService.moveImage(image, 'company');
      }

      if (
        shouldClearImage &&
        company.logo &&
        typeof company.logo === 'object' &&
        !Array.isArray(company.logo) &&
        'file_name' in company.logo &&
        'original_name' in company.logo &&
        'file_size' in company.logo &&
        'mime_type' in company.logo
      ) {
        await this.uploadService.removeImage(company.logo as any);
      }

      const updatedCompany = await this.db.company.update({
        where: { id },
        data: {
          ...data,
          logo: image ?? {
            file_name: profile,
            original_name: 'profile.webp',
            file_size: 8192,
            mime_type: 'image/webp',
          },
        },
      });
      const parsedResult = companyInfoResponseSchema.safeParse(updatedCompany);
      if (!parsedResult.success) {
        throw createGeneralError(
          parsedResult.error.message,
          'company',
          HttpStatus.BAD_REQUEST,
        );
      }
      const parsedData = parsedResult.data;
      return {
        data: parsedData,
        message: 'Company information updated successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in updateCompanyInfo: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async updateWorkSchedule(
    id: string,
    owner_id: string,
    data: UpdateWorkScheduleDto,
    ctx: PermissionUserContext,
  ): Promise<{
    data: WorkScheduleResponseDto;
    message: string;
    status: HttpStatus;
    details: null;
  }> {
    try {
      await this.assertSettingsAccess(ctx, true);
      const schedule = await this.db.working_schedule.findFirst({
        where: { id, owner_id },
      });
      if (!schedule) {
        throw createGeneralError(
          'Work schedule not found',
          'root',
          HttpStatus.NOT_FOUND,
        );
      }
      const updatedSchedule = await this.db.working_schedule.update({
        where: { id },
        data: {
          ...data,
        },
      });
      const parsedResult =
        workScheduleResponseSchema.safeParse(updatedSchedule);
      if (!parsedResult.success) {
        throw createGeneralError(
          parsedResult.error.message,
          'working_schedule',
          HttpStatus.BAD_REQUEST,
        );
      }
      const parsedData = parsedResult.data;
      logger.info(
        `Work schedule updated successfully: ${JSON.stringify(parsedData)}`,
      );
      this.orgTimezone.invalidate(owner_id);
      return {
        data: parsedData,
        message: 'Work schedule updated successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in updateWorkSchedule: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async updateLeavePolicy(
    id: string,
    owner_id: string,
    data: UpdateLeavePolicyDto,
    ctx: PermissionUserContext,
  ): Promise<{
    data: LeavePolicyResponseDto;
    message: string;
    status: HttpStatus;
    details: null;
  }> {
    try {
      await this.assertSettingsAccess(ctx, true);
      const schedule = await this.db.leave_policy.findFirst({
        where: { id, owner_id },
      });
      if (!schedule) {
        throw createGeneralError(
          'Leave policy not found',
          'root',
          HttpStatus.NOT_FOUND,
        );
      }
      const updatedSchedule = await this.db.leave_policy.update({
        where: { id },
        data: {
          ...data,
        },
      });
      const parsedResult = leavePolicyResponseSchema.safeParse(updatedSchedule);
      if (!parsedResult.success) {
        throw createGeneralError(
          parsedResult.error.message,
          'leave_policy',
          HttpStatus.BAD_REQUEST,
        );
      }
      const parsedData = parsedResult.data;
      logger.info(
        `Leave policy updated successfully: ${JSON.stringify(parsedData)}`,
      );
      return {
        data: parsedData,
        message: 'Leave policy updated successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in updateLeavePolicy: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async getWorkDaySchedules(
    owner_id: string,
    ctx: PermissionUserContext,
  ) {
    try {
      await this.assertSettingsAccess(ctx, false);

      let schedules = await this.db.work_day_schedules.findMany({
        where: { owner_id },
        orderBy: { day_of_week: 'asc' },
      });

      // If schedules don't exist yet for this owner, seed all 7 days (0=Sun .. 6=Sat)
      if (!schedules || schedules.length === 0) {
        const defaultDays = [
          { day: 0, active: true }, // Sun
          { day: 1, active: true }, // Mon
          { day: 2, active: true }, // Tue
          { day: 3, active: true }, // Wed
          { day: 4, active: true }, // Thu
          { day: 5, active: false }, // Fri (weekend default)
          { day: 6, active: false }, // Sat (weekend default)
        ];

        for (const item of defaultDays) {
          await this.db.work_day_schedules.create({
            data: {
              owner_id,
              day_of_week: item.day,
              is_active: item.active,
              start_time: '09:00',
              end_time: '17:00',
              break_start: '13:00',
              break_end: '14:00',
              grace_period_minutes: 15,
            },
          });
        }

        schedules = await this.db.work_day_schedules.findMany({
          where: { owner_id },
          orderBy: { day_of_week: 'asc' },
        });
      }

      return {
        status: HttpStatus.OK,
        message: 'Work day schedules retrieved successfully',
        data: schedules,
      };
    } catch (error) {
      logger.error(`Error in getWorkDaySchedules: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async updateWorkDaySchedules(
    owner_id: string,
    body: { schedules: any[] },
    ctx: PermissionUserContext,
  ) {
    try {
      await this.assertSettingsAccess(ctx, true);

      for (const item of body.schedules) {
        const existing = await this.db.work_day_schedules.findFirst({
          where: {
            owner_id,
            day_of_week: item.day_of_week,
          },
        });

        if (existing) {
          await this.db.work_day_schedules.update({
            where: { id: existing.id },
            data: {
              is_active: item.is_active,
              start_time: item.start_time,
              end_time: item.end_time,
              break_start: item.break_start ?? null,
              break_end: item.break_end ?? null,
              grace_period_minutes: item.grace_period_minutes ?? 15,
            },
          });
        } else {
          await this.db.work_day_schedules.create({
            data: {
              owner_id,
              day_of_week: item.day_of_week,
              is_active: item.is_active,
              start_time: item.start_time,
              end_time: item.end_time,
              break_start: item.break_start ?? null,
              break_end: item.break_end ?? null,
              grace_period_minutes: item.grace_period_minutes ?? 15,
            },
          });
        }
      }

      const allSchedules = await this.db.work_day_schedules.findMany({
        where: { owner_id },
        orderBy: { day_of_week: 'asc' },
      });

      return {
        status: HttpStatus.OK,
        message: 'Work day schedules updated successfully',
        data: allSchedules,
      };
    } catch (error) {
      logger.error(`Error in updateWorkDaySchedules: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async getAttendanceValidationSettings(
    owner_id: string,
    _ctx?: PermissionUserContext,
  ): Promise<{
    data: AttendanceValidationSettingsResponseDto;
    message: string;
    status: HttpStatus;
    details: null;
  }> {
    try {
      let settings = await this.db.attendance_settings.findFirst({
        where: { owner_id },
      });

      if (!settings) {
        settings = await this.db.attendance_settings.create({
          data: {
            owner_id,
            enforce_ip_whitelist: false,
            enforce_location: false,
            location_name: 'Main Campus',
            latitude: null,
            longitude: null,
            radius_meters: 100,
          },
        });
      }

      const { data, error } =
        attendanceValidationSettingsResponseSchema.safeParse(settings);

      if (error) {
        throw new ErrorResponse(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
      }

      return {
        data: data as AttendanceValidationSettingsResponseDto,
        message: 'Attendance validation settings fetched successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in getAttendanceValidationSettings: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async updateAttendanceValidationSettings(
    owner_id: string,
    payload: UpdateAttendanceValidationSettingsDto,
    ctx?: PermissionUserContext,
  ): Promise<{
    data: AttendanceValidationSettingsResponseDto;
    message: string;
    status: HttpStatus;
    details: null;
  }> {
    try {
      if (ctx) {
        await this.assertSettingsAccess(ctx, true);
      }

      const existing = await this.db.attendance_settings.findFirst({
        where: { owner_id },
      });

      let updated;
      if (existing) {
        updated = await this.db.attendance_settings.update({
          where: { id: existing.id },
          data: {
            ...(payload.enforce_ip_whitelist !== undefined
              ? { enforce_ip_whitelist: payload.enforce_ip_whitelist }
              : {}),
            ...(payload.enforce_location !== undefined
              ? { enforce_location: payload.enforce_location }
              : {}),
            ...(payload.location_name !== undefined
              ? { location_name: payload.location_name.trim() }
              : {}),
            ...(payload.latitude !== undefined
              ? { latitude: payload.latitude }
              : {}),
            ...(payload.longitude !== undefined
              ? { longitude: payload.longitude }
              : {}),
            ...(payload.radius_meters !== undefined
              ? { radius_meters: payload.radius_meters }
              : {}),
          },
        });
      } else {
        updated = await this.db.attendance_settings.create({
          data: {
            owner_id,
            enforce_ip_whitelist: payload.enforce_ip_whitelist ?? false,
            enforce_location: payload.enforce_location ?? false,
            location_name: payload.location_name?.trim() || 'Main Campus',
            latitude: payload.latitude ?? null,
            longitude: payload.longitude ?? null,
            radius_meters: payload.radius_meters ?? 100,
          },
        });
      }

      const { data, error } =
        attendanceValidationSettingsResponseSchema.safeParse(updated);

      if (error) {
        throw new ErrorResponse(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
      }

      return {
        data: data as AttendanceValidationSettingsResponseDto,
        message: 'Attendance validation settings updated successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(
        `Error in updateAttendanceValidationSettings: ${String(error)}`,
      );
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }
}
