import { Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { SessionDataProps } from 'src/utils/decorators';
import { MenuGroupsEntity } from 'src/entities/MenuGroups.entity';
import { TimezoneMastersEntity } from 'src/entities/TimezoneMasters.entity';
import { MenuGroup } from 'src/constants/types/menus';
import { FavoriteMenuEntity } from 'src/entities/FavoriteMenu.entity';
import { plainToInstance } from 'class-transformer';
import { CreateMenuBookmarkDto } from '../dto/create-menu-bookmark.dto';

/**
 * Panel Settings Service
 *
 * This service handles business logic related to user panel settings and preferences.
 * It manages:
 * - User-accessible menu items based on permissions
 * - Timezone data retrieval
 * - Menu bookmarks (favorites) management
 *
 * The service interacts with multiple entities and repositories to provide
 * personalized settings for each user based on their roles and permissions.
 *
 * @Injectable - Marks this class as a NestJS injectable service
 */
@Injectable()
export class PanelSettingsService {
  constructor(
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    @InjectRepository(MenuGroupsEntity)
    private readonly menuGroupRepo: Repository<MenuGroupsEntity>,
    @InjectRepository(TimezoneMastersEntity)
    private readonly timezoneMasterRepo: Repository<TimezoneMastersEntity>,
    @InjectRepository(FavoriteMenuEntity)
    private readonly favMenuRepo: Repository<FavoriteMenuEntity>,
  ) {}

  /**
   * Fetches menu items accessible to the current user based on their permissions.
   *
   * This method builds a hierarchical menu structure (Groups → Menus → Actions)
   * filtered by the user's specific permissions. Only menu items that the user
   * has explicit permission to access are returned.
   *
   * @param clientData - Session data containing user information
   * @param permitted - Object mapping controller names to allowed action arrays
   * @returns Filtered menu groups with only accessible items
   *
   * @example
   * // Input permissions structure:
   * const permitted = {
   *   'UserController': ['read', 'create'],
   *   'ReportController': ['view']
   * };
   *
   * // Returns menu groups where only 'read' and 'create' actions
   * // are included for UserController menus, etc.
   */
  async fetchUserAccessibleMenuItems(
    clientData: SessionDataProps,
    permitted: Record<string, string[]>,
  ) {
    // Query to fetch all active menu groups with their nested relationships
    const items = await this.menuGroupRepo
      .createQueryBuilder('group')
      .leftJoinAndSelect('group.menus', 'menu') // Join menus within groups
      .leftJoinAndSelect('menu.actions', 'actions') // Join actions within menus
      .where('group.active = :isActive', { isActive: 1 })
      .andWhere('menu.active = :isActive', { isActive: 1 })
      .andWhere('actions.active = :isActive', { isActive: 1 })
      .andWhere('actions.is_visible = :isVisible', { isVisible: 1 })
      .orderBy('actions.order_no', 'ASC') // Sort actions by display order
      .addOrderBy('group.order_no', 'ASC') // Sort groups by display order
      .addOrderBy('menu.order_no', 'ASC') // Sort menus by display order
      .getMany();

    // Filter menu structure based on user permissions
    return this.filterMenuGroups(items as any, permitted);
  }

  /**
   * Fetches all available timezones with associated country information.
   *
   * This method returns a comprehensive list of timezones, typically used
   * for populating timezone selection dropdowns in user settings.
   *
   * @param clientData - Session data (currently unused but available for future extensions)
   * @returns Array of timezone entities with country relations
   *
   * @example
   * // Returns:
   * [
   *   {
   *     timezone_id: 'America/New_York',
   *     timezone_name: 'Eastern Time',
   *     utc_offset: '-05:00',
   *     country: { country_id: 'US', country_name: 'United States' }
   *   },
   *   // ... other timezones
   * ]
   */
  async fetchTimezones(
    clientData: SessionDataProps,
  ): Promise<TimezoneMastersEntity[]> {
    return await this.timezoneMasterRepo
      .createQueryBuilder('timezone')
      .leftJoinAndSelect('timezone.country', 'country') // Include country data
      .orderBy('timezone.country_name', 'ASC') // Sort by country name
      .groupBy('timezone.timezone_id') // Ensure unique timezones
      .getMany();
  }

  /**
   * Filters menu groups based on user permissions.
   *
   * This private method recursively filters the menu hierarchy:
   * 1. Filters actions within each menu based on allowed actions
   * 2. Removes menus that have no accessible actions
   * 3. Removes groups that have no accessible menus
   *
   * @private
   * @param menuGroups - Full menu hierarchy from database
   * @param permitted - User's permission mapping
   * @returns Filtered menu hierarchy
   */
  private filterMenuGroups(
    menuGroups: MenuGroup[],
    permitted: Record<string, string[]>,
  ): MenuGroup[] {
    return menuGroups
      .map((group) => {
        // Filter menus within this group
        const filteredMenus = group.menus
          .map((menu) => {
            // Get allowed actions for this menu's controller
            const allowedActions = permitted[menu.controller] ?? [];

            // Filter actions to only include permitted ones
            const filteredActions = menu.actions.filter((action) =>
              allowedActions.includes(action.action),
            );

            // Return menu with filtered actions
            return { ...menu, actions: filteredActions };
          })
          .filter((menu) => menu.actions.length > 0); // Remove menus with no accessible actions

        // Return group with filtered menus
        return { ...group, menus: filteredMenus };
      })
      .filter((group) => group.menus.length > 0); // Remove groups with no accessible menus
  }

  /**
   * Manages user menu bookmarks (add/delete operations).
   *
   * This method handles the creation and deletion of menu bookmarks.
   * Bookmarks are stored per user and can be added or removed based on
   * the action specified in the DTO.
   *
   * @param userId - ID of the user managing bookmarks
   * @param createDto - Bookmark operation details
   * @returns Updated list of user's bookmarks
   *
   * @throws {Error} May throw if database operations fail
   *
   * @note There's a discrepancy: DTO uses 'delete' action but enum likely expects 'remove'
   *
   * @example
   * // Adding a bookmark:
   * await manageBookmarkMenu(123, {
   *   menu_link: '/users',
   *   menu_name: 'User Management',
   *   action: 'add'
   * });
   *
   * @example
   * // Deleting a bookmark by ID:
   * await manageBookmarkMenu(123, {
   *   id: 456,
   *   menu_link: '/users',
   *   menu_name: 'User Management',
   *   action: 'delete' // Note: should likely be 'remove'
   * });
   */
  async manageBookmarkMenu(userId: number, createDto: CreateMenuBookmarkDto) {
    const menuLink = createDto.menu_link;

    // Determine lookup condition: by ID if provided, otherwise by user+menu_link
    const condition = createDto.id
      ? { id: createDto.id } // Lookup by specific bookmark ID
      : { user_id: userId, menu_link: menuLink }; // Lookup by user and menu link

    // Check if bookmark already exists
    const existing = await this.favMenuRepo.findOne({
      where: condition,
    });

    // Add new bookmark if it doesn't exist and action is 'add'
    if (!existing && createDto.action === 'add') {
      const bookmark = plainToInstance(FavoriteMenuEntity, {
        ...createDto,
        user_id: userId,
        platform: 2, // Platform identifier (e.g., 2 = web admin panel)
      });
      await this.favMenuRepo.save(bookmark);
    }

    // Delete existing bookmark if action is 'delete' and ID is provided
    // NOTE: Action should likely be 'remove' to match DTO enum
    if (existing && createDto.action === 'delete' && createDto.id) {
      await this.favMenuRepo.delete({ id: createDto?.id });
    }

    // Return updated list of user's bookmarks
    return await this.favMenuRepo.find({
      where: { user_id: userId },
      order: { created: 'DESC' }, // Most recent first
    });
  }
}
