// Import NestJS decorators and utilities
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';

// Import TypeORM decorators for dependency injection
import { InjectRepository } from '@nestjs/typeorm';

// Import TypeORM core classes
import { DataSource, Repository } from 'typeorm';

// Import entity representing the database table
import { MenuGroupsEntity } from 'src/entities/MenuGroups.entity';

// Import DTOs for validation and type safety
import { CreateDto } from './dto/create.dto';

// Import class-transformer for object transformation
import { plainToInstance } from 'class-transformer';

// Import pagination utilities
import {
  getSkipOffset,
  paginate,
} from 'src/config/pagination/pagination.service';

// Import other DTOs
import { SearchDto } from './dto/search-dto';
import { UpdateDto } from './dto/update.dto';

// Import pagination response type
import { Response } from 'src/config/pagination/paginationDto';

// Import cache-related dependencies
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';

// Import status update DTO
import { UpdateStatusDto } from './dto/update-status.dto';

/**
 * MenuGroupsService - Service layer for menu groups business logic
 *
 * This service handles all data operations for menu groups including:
 * - CRUD operations
 * - Search and filtering
 * - Pagination
 * - Caching
 * - Business rule validation
 *
 * @Injectable() - Makes this class injectable as a provider throughout the application
 */
@Injectable()
export class MenuGroupsService {
  /**
   * Constructor with dependency injection
   *
   * @param datasource - TypeORM DataSource for complex transactions or queries
   * @param cacheManager - Cache manager for caching operations (injected via @Inject)
   * @param groupRepo - Repository pattern for MenuGroupsEntity database operations
   */
  constructor(
    private datasource: DataSource,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    @InjectRepository(MenuGroupsEntity)
    private readonly groupRepo: Repository<MenuGroupsEntity>,
  ) {}

  /**
   * findAll - Retrieves paginated menu groups with comprehensive filtering
   *
   * Builds a complex query with multiple filtering options including:
   * - Text search on name
   * - Filter by creator/updater users
   * - Filter by active status
   * - Date range filters for creation and modification
   *
   * @param searchDto - Contains all search, filter, and pagination parameters
   * @returns Promise<Response<MenuGroupsEntity>> - Paginated response with data and pagination metadata
   */
  async findAll(searchDto: SearchDto): Promise<Response<MenuGroupsEntity>> {
    const query = this.groupRepo
      .createQueryBuilder('group')
      .leftJoin('group.user', 'user')
      .addSelect(['user.fname', 'user.lname', 'user.employeeCode'])
      .leftJoin('group.modified_by', 'modified_by')
      .addSelect([
        'modified_by.fname',
        'modified_by.lname',
        'modified_by.employeeCode',
      ])
      .orderBy('group.id', 'DESC');

    // Text search filter
    if (searchDto.search_title) {
      query.andWhere('group.name LIKE :search_title', {
        search_title: `%${searchDto.search_title}%`,
      });
    }

    // Filter by creators (multiple user IDs)
    if (searchDto.created_by && searchDto.created_by.length > 0) {
      query.andWhere('group.by_user IN (:...created_by)', {
        created_by: searchDto.created_by,
      });
    }

    // Filter by updaters (multiple user IDs)
    if (searchDto.updated_by && searchDto.updated_by.length > 0) {
      query.andWhere('group.modified_by_user IN (:...updated_by)', {
        updated_by: searchDto.updated_by,
      });
    }

    // Filter by active status (multiple status values)
    if (searchDto.active && searchDto.active.length > 0) {
      query.andWhere('group.active IN (:...active)', {
        active: searchDto.active,
      });
    }

    // Date range filter for creation date
    if (searchDto.created_on) {
      const { from, to } = searchDto.created_on;
      if (from && to) {
        query.andWhere('group.created BETWEEN :createdFrom AND :createdTo', {
          createdFrom: from,
          createdTo: to,
        });
      } else if (from) {
        query.andWhere('group.created >= :createdFrom', {
          createdFrom: from,
        });
      } else if (to) {
        query.andWhere('group.created <= :createdTo', {
          createdTo: to,
        });
      }
    }

    // Date range filter for modification date
    if (searchDto.updated_on) {
      const { from, to } = searchDto.updated_on;
      if (from && to) {
        query.andWhere('group.modified BETWEEN :modifiedFrom AND :modifiedTo', {
          modifiedFrom: from,
          modifiedTo: to,
        });
      } else if (from) {
        query.andWhere('group.modified >= :modifiedFrom', {
          modifiedFrom: from,
        });
      } else if (to) {
        query.andWhere('group.modified <= :modifiedTo', {
          modifiedTo: to,
        });
      }
    }

    // Apply pagination
    const [skip, limit] = getSkipOffset(searchDto);
    const [data, total] = await query.skip(skip).take(limit).getManyAndCount();
    const pagination = paginate(total, searchDto.per_page, searchDto.page);

    return { list: data, pagination };
  }

  /**
   * findAllActive - Retrieves all active menu groups (lightweight query)
   *
   * Used for dropdowns or selection lists where only basic info is needed
   * and only active items should be shown.
   *
   * @returns Promise<Response<MenuGroupsEntity>> - List of active menu groups with minimal fields
   */
  async findAllActive(): Promise<Response<MenuGroupsEntity>> {
    const data = await this.groupRepo
      .createQueryBuilder('group')
      .select(['group.id', 'group.name']) // Select only necessary fields
      .where('group.active = :active', { active: 1 })
      .orderBy('group.name', 'ASC')
      .getMany();
    return { list: data };
  }

  /**
   * findOne - Retrieves a single menu group by ID including related menus
   *
   * @param id - The ID of the menu group to retrieve
   * @returns The menu group entity with its related menus
   */
  findOne(id: number) {
    return this.groupRepo.findOne({ where: { id }, relations: ['menus'] });
  }

  /**
   * create - Creates a new menu group
   *
   * Business rules:
   * 1. Name must be unique
   * 2. Creator (user) must be tracked
   *
   * @param userId - ID of the user creating the menu group
   * @param createDto - Validated creation data
   * @returns The newly created menu group entity
   * @throws HttpException - If menu group already exists or other error occurs
   */
  async create(userId: number, createDto: CreateDto) {
    try {
      // Check for duplicate name
      const exists = await this.groupRepo.findOne({
        where: { name: createDto.name },
      });
      if (exists) throw new Error('Menu Group already exists.');

      // Transform DTO to entity with creator info
      const menuGroupEntity = plainToInstance(MenuGroupsEntity, {
        ...createDto,
        by_user: userId,
      });

      // Save to database
      return await this.groupRepo.save(menuGroupEntity);
    } catch (error) {
      // Consistent error handling with appropriate status codes
      throw new HttpException(
        error?.message || 'Something went wrong while creating',
        error?.status || HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }

  /**
   * delete - Deletes a menu group by ID
   *
   * Note: Uses soft delete? (Not shown, might be implemented at entity level)
   *
   * @param id - ID of the menu group to delete
   * @returns Delete result
   */
  delete(id: number) {
    return this.groupRepo.delete(id);
  }

  /**
   * update - Updates an existing menu group
   *
   * Business rules:
   * 1. ID must be provided
   * 2. Name must be unique (cannot duplicate with other records)
   * 3. Updater must be tracked
   *
   * @param userId - ID of the user performing the update
   * @param updateDto - Validated update data including ID
   * @returns Update result
   * @throws HttpException - If validation fails or record not found
   */
  async update(userId: string, updateDto: UpdateDto) {
    const id = updateDto.id;

    if (!id) throw new HttpException('Id is required', HttpStatus.BAD_REQUEST);

    try {
      // Check for duplicate name with different ID
      const exists = await this.groupRepo.findOne({
        where: { name: updateDto.name },
      });
      console.log({ exists });
      if (exists && exists?.id !== Number(id))
        throw new Error('Menu Group already exists with another Id.');

      // Transform DTO to entity with updater info
      const menuGroupsEntity = plainToInstance(MenuGroupsEntity, {
        ...updateDto,
        modified_by_user: userId,
      });

      // Perform update
      const result = await this.groupRepo.update(id, menuGroupsEntity);

      // Check if record was actually updated
      if (result.affected === 0) {
        throw new HttpException(
          `No record found with ID ${id} to update`,
          HttpStatus.NOT_FOUND,
        );
      }

      return result;
    } catch (error) {
      console.error('Error while updating Menu Group:', error);

      throw new HttpException(
        error?.message || 'Something went wrong while updating',
        error?.status || HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }

  /**
   * fetchCreatedByOrUpdatedByUsers - Retrieves distinct users who created or updated menu groups
   *
   * Used for filter dropdowns in UI to show users involved in menu group management.
   *
   * @param by - Filter type: 'created_by' or 'updated_by'
   * @returns Array of users with formatted display names
   */
  async fetchCreatedByOrUpdatedByUsers(by: string) {
    const query = this.groupRepo.createQueryBuilder('group').select('group.id');

    if (by === 'created_by') {
      query
        .leftJoin('group.user', 'user')
        .addSelect(`user.id`, 'id')
        .addSelect(
          `CONCAT(user.fname, ' ', user.lname, ' - ', user.employeeCode)`,
          'name',
        )
        .orderBy('user.fname', 'ASC')
        .addOrderBy('user.lname', 'ASC');
    }

    if (by === 'updated_by') {
      query
        .leftJoin('group.modified_by', 'modified_by')
        .addSelect(`modified_by.id`, 'id')
        .addSelect(
          `CONCAT(modified_by.fname, ' ', modified_by.lname, ' - ', modified_by.employeeCode)`,
          'name',
        )
        .where('group.modified_by IS NOT NULL')
        .orderBy('modified_by.fname', 'ASC')
        .addOrderBy('modified_by.lname', 'ASC');
    }
    query.groupBy('employeeCode'); // Ensure distinct users

    return await query.getRawMany(); // Returns raw data for formatted names
  }

  /**
   * updateStatus - Bulk updates status of multiple menu groups
   *
   * Used for activating/deactivating menu groups in bulk.
   * Tracks which user performed the status change.
   *
   * @param userId - ID of user performing the status update
   * @param updateStatusDto - Contains IDs and action (activate/deactivate)
   * @returns Update result
   */
  async updateStatus(userId: string, updateStatusDto: UpdateStatusDto) {
    const { ids, action } = updateStatusDto;

    // Execute bulk update query
    return await this.groupRepo
      .createQueryBuilder()
      .update()
      .set({
        active: action === 'activate' ? 1 : 2, // Status mapping
        modified_by_user: Number(userId),
      })
      .where('id IN (:...ids)', { ids })
      .execute();
  }
}
