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 {
  createLocationDto,
  locationResponseSchema,
  locationsResponseSchema,
  updateLocationDto,
} from './locations.dto';

@Injectable()
export class LocationsService {
  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 createLocation(
    data: createLocationDto,
    owner_id: string,
    ctx: PermissionUserContext,
  ) {
    try {
      await this.assertGeneralAccess(ctx, true);
      const existing = await this.db.locations.findFirst({
        where: {
          location_code: data.location_code,
          owner_id,
        },
      });

      if (existing) {
        throw createGeneralError(
          'Location with same name or code already exists',
          'location_code',
          HttpStatus.CONFLICT,
        );
      }

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

      const { data: parsed } = locationResponseSchema.safeParse(location);

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

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

      const { data: parsed } = locationsResponseSchema.safeParse(locations);

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

  async getFilteredLocations(
    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 } },
          { location_code: { contains: query.search } },
        ];
      }

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

      const { data: parsed } = locationsResponseSchema.safeParse(locations);

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

      return {
        data: parsed,
        message: 'Locations 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 getFilteredLocations: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async getLocationById(
    id: string,
    owner_id: string,
    ctx: PermissionUserContext,
  ) {
    try {
      await this.assertGeneralAccess(ctx);
      const location = await this.db.locations.findUnique({
        where: { id, owner_id },
      });

      if (!location) {
        throw createGeneralError(
          'Location not found',
          'location',
          HttpStatus.NOT_FOUND,
        );
      }

      const { data: parsed } = locationResponseSchema.safeParse(location);

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

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

      if (!existing) {
        throw createGeneralError(
          'Location not found',
          'location',
          HttpStatus.NOT_FOUND,
        );
      }

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

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

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

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

      if (!existing) {
        throw createGeneralError(
          'Location not found',
          'location',
          HttpStatus.NOT_FOUND,
        );
      }

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

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

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

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

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

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