// 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 entities representing database tables
import { MenusEntity } from 'src/entities/Menus.entity';

// Import DTOs for validation and type safety
import { CreateMenuDto } 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 { SearchMenuDto } from './dto/search-dto';
import { UpdateMenuDto } 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';

// Import related entity for menu actions
import { MenuActionsEntity } from 'src/entities/MenuActions.entity';

/**
 * MenusService - Service layer for menu management business logic
 *
 * This service handles all data operations for menus including:
 * - CRUD operations for menus
 * - Management of related menu actions (permissions/operations)
 * - Complex search and filtering with pagination
 * - Transaction management for data consistency
 * - Audit tracking and user activity logging
 *
 * Key Features:
 * - Transaction support for complex operations
 * - Relationship management with menu groups and actions
 * - Bulk operations for status updates
 * - Comprehensive filtering capabilities
 *
 * @Injectable() - Makes this class injectable as a provider throughout the application
 */
@Injectable()
export class MenusService {
  /**
   * Constructor with dependency injection
   *
   * @param dataSource - TypeORM DataSource for transaction management
   * @param cacheManager - Cache manager for caching operations
   * @param menuRepo - Repository for MenusEntity database operations
   */
  constructor(
    private dataSource: DataSource,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    @InjectRepository(MenusEntity)
    private readonly menuRepo: Repository<MenusEntity>,
  ) {}

  /**
   * findAll - Retrieves paginated menus with comprehensive filtering and joins
   *
   * Builds a complex query with multiple joins and filtering options:
   * - Joins with menu groups, creators, updaters, and actions
   * - Text search on title and controller fields
   * - 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<MenusEntity>> - Paginated response with data and pagination metadata
   */
  async findAll(searchDto: SearchMenuDto): Promise<Response<MenusEntity>> {
    const query = this.menuRepo
      .createQueryBuilder('menu')
      // Join with menu group for grouping information
      .leftJoin('menu.group', 'group')
      .addSelect(['group.id', 'group.name'])
      // Join with creator user for audit info
      .leftJoin('menu.user', 'user')
      .addSelect(['user.fname', 'user.lname', 'user.employeeCode'])
      // Join with updater user for audit info
      .leftJoin('menu.modified_by', 'modified_by')
      .addSelect([
        'modified_by.fname',
        'modified_by.lname',
        'modified_by.employeeCode',
      ])
      // Join with menu actions (permissions/operations)
      .leftJoinAndSelect('menu.actions', 'actions')
      .orderBy('menu.id', 'DESC'); // Default ordering

    // Text search on multiple fields
    if (searchDto.search_title) {
      query.andWhere(
        '(menu.title LIKE :search OR menu.controller LIKE :search)',
        { search: `%${searchDto.search_title}%` },
      );
    }

    // Filter by menu_group_id (multiple group IDs)
    if (searchDto.menu_groups && searchDto.menu_groups.length > 0) {
      query.andWhere('menu.menu_group_id IN (:...menu_groups)', {
        menu_groups: searchDto.menu_groups,
      });
    }

    // Filter by creators (multiple user IDs)
    if (searchDto.created_by && searchDto.created_by.length > 0) {
      query.andWhere('menu.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('menu.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('menu.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('menu.created BETWEEN :createdFrom AND :createdTo', {
          createdFrom: from,
          createdTo: to,
        });
      } else if (from) {
        query.andWhere('menu.created >= :createdFrom', {
          createdFrom: from,
        });
      } else if (to) {
        query.andWhere('menu.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('menu.modified BETWEEN :modifiedFrom AND :modifiedTo', {
          modifiedFrom: from,
          modifiedTo: to,
        });
      } else if (from) {
        query.andWhere('menu.modified >= :modifiedFrom', {
          modifiedFrom: from,
        });
      } else if (to) {
        query.andWhere('menu.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 };
  }

  /**
   * findOne - Retrieves a single menu by ID with all related entities
   *
   * Fetches a menu with its related group, creator, and updater information.
   *
   * @param id - The ID of the menu to retrieve
   * @returns The menu entity with all related data
   */
  findOne(id: number) {
    return this.menuRepo.findOne({
      where: { id },
      relations: ['group', 'user', 'modified_by'],
    });
  }

  /**
   * create - Creates a new menu with associated actions using transactions
   *
   * Business rules:
   * 1. Menu must have unique controller name within a menu group
   * 2. Supports creating menu actions in the same transaction
   * 3. Uses transaction for data consistency
   *
   * @param userId - ID of the user creating the menu
   * @param createDto - Validated creation data including actions
   * @returns The newly created menu entity with its actions
   * @throws HttpException - If validation fails or transaction error
   */
  async create(userId: number, createDto: CreateMenuDto) {
    // Create query runner for transaction management
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // Check for duplicate menu (controller + menu_group_id combination)
      const exists = await queryRunner.manager
        .getRepository(MenusEntity)
        .createQueryBuilder('menu')
        .where('menu.controller = :controller', {
          controller: createDto.controller,
        })
        .andWhere('menu.menu_group_id = :menuGroupId', {
          menuGroupId: Number(createDto.menu_group_id),
        })
        .getOne();

      if (exists) {
        throw new Error('Menu already exists.');
      }

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

      // Save menu entity
      const savedMenu = await queryRunner.manager.save(MenusEntity, menuEntity);

      // Create associated menu actions if provided
      if (createDto.actions && createDto.actions.length > 0) {
        const actions = createDto.actions.map((action) =>
          plainToInstance(MenuActionsEntity, {
            ...action,
            menu_id: savedMenu.id,
            by_user: userId,
          }),
        );
        await queryRunner.manager.save(MenuActionsEntity, actions);
      }

      // Commit transaction
      await queryRunner.commitTransaction();

      // Return the complete menu with actions
      return await queryRunner.manager.findOne(MenusEntity, {
        where: { id: savedMenu.id },
        relations: ['actions'],
      });
    } catch (error) {
      // Rollback on error
      await queryRunner.rollbackTransaction();
      throw new HttpException(
        error?.message || 'Something went wrong while creating',
        error?.status || HttpStatus.INTERNAL_SERVER_ERROR,
      );
    } finally {
      // Always release query runner
      await queryRunner.release();
    }
  }

  /**
   * update - Updates an existing menu and its actions using transactions
   *
   * Business rules:
   * 1. ID must be provided
   * 2. Menu must exist
   * 3. Actions are completely replaced (delete all, insert new)
   *
   * @param userId - ID of the user performing the update
   * @param updateDto - Validated update data including actions
   * @returns The updated menu entity with its actions
   * @throws HttpException - If validation fails or transaction error
   */
  async update(userId: number, updateDto: UpdateMenuDto) {
    const id = updateDto.id;

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

    // Create query runner for transaction management
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // Verify menu exists
      const menu = await queryRunner.manager.findOne(MenusEntity, {
        where: { id: Number(id) },
      });
      if (!menu) throw new Error('Menu not found');

      // Separate actions from menu data
      const { actions, ...menuData } = updateDto;

      // Prepare update data with proper type conversions
      const updateData = {
        ...menuData,
        menu_group_id: Number(menuData.menu_group_id),
        order_no: Number(menuData.order_no),
        is_visible: Number(menuData.is_visible),
        active: Number(menuData.active),
        modified_by_user: userId, // Track updater
      };

      // Update menu entity
      await queryRunner.manager.update(MenusEntity, id, updateData);

      // Handle menu actions (complete replacement strategy)
      if (actions) {
        // Delete all existing actions
        await queryRunner.manager.delete(MenuActionsEntity, { menu_id: id });

        // Insert new actions if any
        if (actions.length > 0) {
          const newActions = actions.map((action) =>
            plainToInstance(MenuActionsEntity, {
              ...action,
              menu_id: id,
              modified_by_user: userId, // Track action updater
            }),
          );
          await queryRunner.manager.save(MenuActionsEntity, newActions);
        }
      }

      // Commit transaction
      await queryRunner.commitTransaction();

      // Return updated menu with actions
      return this.menuRepo.findOne({
        where: { id },
        relations: ['actions'],
      });
    } catch (error) {
      // Rollback on error
      await queryRunner.rollbackTransaction();
      throw new HttpException(
        error?.message || 'Error while updating Menu',
        error?.status || HttpStatus.INTERNAL_SERVER_ERROR,
      );
    } finally {
      // Always release query runner
      await queryRunner.release();
    }
  }

  /**
   * delete - Deletes a menu by ID
   *
   * Note: This is likely a hard delete. Consider if cascade deletion
   * or soft delete would be more appropriate.
   *
   * @param id - ID of the menu to delete
   * @returns Delete result
   */
  delete(id: number) {
    return this.menuRepo.delete(id);
  }

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

    if (by === 'created_by') {
      query
        .leftJoin('menu.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('menu.modified_by', 'modified_by')
        .addSelect(`modified_by.id`, 'id')
        .addSelect(
          `CONCAT(modified_by.fname, ' ', modified_by.lname, ' - ', modified_by.employeeCode)`,
          'name',
        )
        .where('menu.modified_by IS NOT NULL')
        .orderBy('modified_by.fname', 'ASC')
        .addOrderBy('modified_by.lname', 'ASC');
    }
    query.groupBy('employeeCode'); // Ensure distinct users by employee code

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

  /**
   * fetchMenuGroupsByList - Retrieves distinct menu groups used by menus
   *
   * Used for filter dropdowns or selection lists in UI.
   *
   * @returns Array of menu groups with ID and name
   */
  async fetchMenuGroupsByList() {
    return await this.menuRepo
      .createQueryBuilder('menu')
      .leftJoin('menu.group', 'group')
      .select(['DISTINCT menu.menu_group_id AS id', 'group.name AS name'])
      .orderBy('group.name', 'ASC')
      .getRawMany();
  }

  /**
   * updateStatus - Bulk updates status of multiple menus
   *
   * Used for activating/deactivating menus 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.menuRepo
      .createQueryBuilder()
      .update()
      .set({
        active: action === 'activate' ? 1 : 2, // Status mapping
        modified_by_user: Number(userId), // Track updater
      })
      .where('id IN (:...ids)', { ids })
      .execute();
  }
}
