import { HttpStatus, Injectable } from '@nestjs/common';
import { assertGeneralModuleAccess } 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, ErrorResponse, logger } from '../../config';
import { createGeneralError } from '../../utils';
import { defaultPaginationDto } from '../shared/shared.dto';
import {
  createHiringSourceDto,
  hiringSourceResponseSchema,
  hiringSourcesResponseSchema,
  updateHiringSourceDto,
} from './hiring_sources.dto';

@Injectable()
export class HiringSourcesService {
  constructor(
    private readonly db: DatabaseService,
    private readonly permissionResolver: PermissionResolverService,
  ) {}

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

  async createHiringSource(
    data: createHiringSourceDto,
    owner_id: string,
    ctx: PermissionUserContext,
  ) {
    try {
      await this.assertGeneralAccess(ctx, true);
      const existing = await this.db.hiring_sources.findFirst({
        where: {
          name: data.name,
          owner_id,
        },
      });

      if (existing) {
        throw createGeneralError(
          'Hiring source with same name already exists',
          'name',
          HttpStatus.CONFLICT,
        );
      }

      const hiringSource = await this.db.hiring_sources.create({
        data: {
          name: data.name,
          ...(data.description ? { description: data.description } : {}),
          ...(data.status ? { status: data.status } : {}),
          owner_id,
        },
      });

      const { data: parsed } =
        hiringSourceResponseSchema.safeParse(hiringSource);

      return {
        data: parsed,
        message: 'Hiring source created successfully',
        status: HttpStatus.CREATED,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in createHiringSource: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async getHiringSources(owner_id: string, ctx: PermissionUserContext) {
    try {
      await this.assertGeneralAccess(ctx);
      const hiringSources = await this.db.hiring_sources.findMany({
        where: {
          owner_id,
          status: {
            not: 'deleted',
          },
        },
        orderBy: { created_at: 'asc' },
      });

      const { data: parsed } =
        hiringSourcesResponseSchema.safeParse(hiringSources);

      return {
        data: parsed,
        message: 'Hiring sources retrieved successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in getHiringSources: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async getFilteredHiringSources(
    owner_id: string,
    query: defaultPaginationDto,
    ctx: PermissionUserContext,
  ) {
    try {
      await this.assertGeneralAccess(ctx);
      const page = query.page ?? 1;
      const limit = query.limit ?? 10;
      const skip = (page - 1) * limit;

      const where: any = {
        owner_id,
        status: {
          not: 'deleted',
        },
      };

      if (query.search) {
        where.OR = [{ name: { contains: query.search } }];
      }

      const [hiringSources, total] = await Promise.all([
        this.db.hiring_sources.findMany({
          where,
          orderBy: { created_at: 'asc' },
          skip,
          take: limit,
        }),
        this.db.hiring_sources.count({ where }),
      ]);

      const { data: parsed } =
        hiringSourcesResponseSchema.safeParse(hiringSources);

      const totalPages = Math.ceil(total / limit);

      return {
        data: parsed,
        message: 'Hiring sources retrieved successfully',
        status: HttpStatus.OK,
        details: {
          pagination: {
            total_items: total,
            total_pages: totalPages,
            current_page: page,
            items_per_page: limit,
            has_next_page: page < totalPages,
            has_previous_page: page > 1,
            next_page: page < totalPages ? page + 1 : null,
            previous_page: page > 1 ? page - 1 : null,
          },
        },
      };
    } catch (error) {
      logger.error(`Error in getFilteredHiringSources: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async getHiringSourceById(
    id: string,
    owner_id: string,
    ctx: PermissionUserContext,
  ) {
    try {
      await this.assertGeneralAccess(ctx);
      const hiringSource = await this.db.hiring_sources.findUnique({
        where: { id, owner_id },
      });

      if (!hiringSource) {
        throw createGeneralError(
          'Hiring source not found',
          'hiring_source',
          HttpStatus.NOT_FOUND,
        );
      }

      const { data: parsed } =
        hiringSourceResponseSchema.safeParse(hiringSource);

      return {
        data: parsed,
        message: 'Hiring source retrieved successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in getHiringSourceById: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async updateHiringSource(
    id: string,
    data: updateHiringSourceDto,
    owner_id: string,
    ctx: PermissionUserContext,
  ) {
    try {
      await this.assertGeneralAccess(ctx, true);
      const existing = await this.db.hiring_sources.findUnique({
        where: { id, owner_id },
      });

      if (!existing) {
        throw createGeneralError(
          'Hiring source not found',
          'hiring_source',
          HttpStatus.NOT_FOUND,
        );
      }

      const updated = await this.db.hiring_sources.update({
        where: { id, owner_id },
        data,
      });

      const { data: parsed } = hiringSourceResponseSchema.safeParse(updated);

      return {
        data: parsed,
        message: 'Hiring source updated successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in updateHiringSource: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async deleteHiringSource(
    id: string,
    owner_id: string,
    ctx: PermissionUserContext,
  ) {
    try {
      await this.assertGeneralAccess(ctx, true);
      const existing = await this.db.hiring_sources.findUnique({
        where: { id, owner_id },
      });

      if (!existing) {
        throw createGeneralError(
          'Hiring source not found',
          'hiring_source',
          HttpStatus.NOT_FOUND,
        );
      }

      const usageCount = await this.db.members.count({
        where: { hiring_source_id: id, owner_id },
      });

      if (usageCount > 0) {
        const softDeleted = await this.db.hiring_sources.update({
          where: { id, owner_id },
          data: {
            status: 'deleted',
          },
        });

        const { data: parsed } =
          hiringSourceResponseSchema.safeParse(softDeleted);

        return {
          data: parsed,
          message: 'Hiring source is in use. Status changed to deleted.',
          status: HttpStatus.OK,
          details: {
            usedByMembers: usageCount,
          },
        };
      }

      await this.db.hiring_sources.delete({
        where: { id, owner_id },
      });

      return {
        data: null,
        message: 'Hiring source deleted successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in deleteHiringSource: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }
}
