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

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

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

// Import entity definitions
import { UserEntity } from 'src/entities/User.entity';

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

// Import more entity definitions
import { UserBranchEntity } from 'src/entities/UserBranch.entity';
import { DivisionDto } from './dto/division.dto';
import { DivisionMastersEntity } from 'src/entities/DivisionMasters.entity';
import { LmsTableSettingsEntity } from 'src/entities/LmsTableSettings.entity';
import { plainToInstance } from 'class-transformer';
import { CountryEntity } from 'src/entities/Country.entity';
import { ICenterLocation, IColumnConfig } from 'src/utils/interfaces/config';
import { UpdateThemeDto } from './dto/update-theme.dto';
import { ThemeEntity } from 'src/entities/Theme.entity';
import { UpdateMenuViewDto } from './dto/update-menu-view.dto';

// Constants for business logic
const DIVISION_LIST = [1, 2]; // Likely default division IDs (e.g., 1=Admin, 2=User)
const ADMIN = 'Super Admin'; // Role name for super administrators

/**
 * UserService - Service layer for comprehensive user management and personalization
 *
 * This service handles all user-related business logic including:
 * - User profile management with caching
 * - Organizational structure access (branches, divisions)
 * - User preference management (themes, menu views, table settings)
 * - Geographic reference data (countries)
 * - Sophisticated caching strategies for performance
 *
 * Business Domain: User Management & Personalization System
 * - Manages user profiles with relational data
 * - Handles organizational hierarchy and access
 * - Implements user preference persistence
 * - Provides caching for performance optimization
 *
 * Key Features:
 * - Comprehensive caching strategy for all queries
 * - Role-based access control for organizational data
 * - User preference management with intelligent defaults
 * - Integration with 6 different entity repositories
 */
@Injectable()
export class UserService {
  /**
   * Constructor with dependency injection for 6 repositories
   */
  constructor(
    private datasource: DataSource,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    @InjectRepository(UserEntity)
    private readonly userRepo: Repository<UserEntity>,
    @InjectRepository(DivisionMastersEntity)
    private readonly divisionMastersRepo: Repository<DivisionMastersEntity>,
    @InjectRepository(UserBranchEntity)
    private readonly userBranchRepo: Repository<UserBranchEntity>,
    @InjectRepository(LmsTableSettingsEntity)
    private readonly lmsTableSettingRepo: Repository<LmsTableSettingsEntity>,
    @InjectRepository(CountryEntity)
    private readonly countryRepo: Repository<CountryEntity>,
    @InjectRepository(ThemeEntity)
    private readonly themeRepo: Repository<ThemeEntity>,
  ) {}

  /**
   * findOne - Retrieves a user by ID with intelligent caching
   *
   * Caching Strategy: Cache-Aside Pattern
   * 1. Check cache first
   * 2. If not in cache, query database
   * 3. Store result in cache
   * 4. Return result
   *
   * Performance Benefits:
   * - Reduces database load for frequently accessed users
   * - Improves response time for user profile queries
   * - Automatic cache population on first request
   *
   * @param id - User ID to retrieve
   * @returns User entity with related designation and center_home
   */
  async findOne(id: number) {
    const cacheKey = `user_${id}`;

    // 1. Check cache first
    const cachedData = await this.cacheManager.get<UserEntity>(cacheKey);
    if (cachedData) {
      return cachedData;
    }

    // 2. Query database if not in cache
    const result = await this.userRepo.findOne({
      where: { id },
      relations: ['designation', 'center_home'], // Eager load related entities
    });

    // 3. Store in cache
    await this.cacheManager.set(cacheKey, result);

    return result;
  }

  /**
   * getFunctionalBranches - Retrieves functional branches for a user
   *
   * Business Logic: Returns distinct center locations (branches)
   * that a user has access to. If no userId provided, returns all branches.
   *
   * Caching: User-specific caching for performance
   *
   * @param userId - Optional user ID to filter branches
   * @returns Array of center locations with ID and name
   */
  async getFunctionalBranches(userId?: number): Promise<ICenterLocation[]> {
    const cacheKey = `user_${userId}_functional_branches`;

    // Check cache
    const cachedData = await this.cacheManager.get<ICenterLocation[]>(cacheKey);
    if (cachedData) {
      return cachedData;
    }

    // Build query with join to center location table
    const query = this.userBranchRepo
      .createQueryBuilder('ub')
      .distinct(true)
      .select(['cl.center_id AS center_id', 'cl.center_name AS center_name'])
      .innerJoin('ub.center', 'cl'); // Join with center location table

    // Filter by user if provided
    if (userId) {
      query.where('ub.user_id = :userId', { userId });
    }

    const result = await query.getRawMany();

    // Cache result
    await this.cacheManager.set(cacheKey, result);

    return result;
  }

  /**
   * getUserDivisionByUserId - Retrieves divisions for a user with role-based logic
   *
   * Complex Business Logic:
   * - Super Admin: Gets all divisions from DIVISION_LIST constant
   * - Regular Users: Gets divisions assigned to them from user_divisions table
   * - Supports optional division filtering
   *
   * Role-Based Access Pattern:
   * - Super Admin bypasses user-division mapping
   * - Regular users restricted to assigned divisions
   *
   * @param userId - User ID
   * @param divisionId - Optional specific division ID
   * @param roleName - User's role name for access control
   * @returns Array of division DTOs
   */
  async getUserDivisionByUserId(
    userId: number,
    divisionId?: number,
    roleName?: string,
  ): Promise<DivisionDto[]> {
    const cacheKey = `user_${userId}_divisions`;

    // Check cache
    const cachedData = await this.cacheManager.get<DivisionDto[]>(cacheKey);
    if (cachedData) {
      return cachedData;
    }

    // Role-based logic: Super Admin gets all divisions
    if (roleName === ADMIN) {
      const divisions = await this.divisionMastersRepo.find({
        where: { id: In(DIVISION_LIST) }, // Use constant division IDs
        select: ['id', 'division_name'],
      });

      return divisions.map((division) => ({
        division_id: division.id,
        division_name: division.division_name,
      }));
    } else {
      // Regular users get assigned divisions
      const query = this.divisionMastersRepo
        .createQueryBuilder('dm')
        .innerJoin('dm.user_divisions', 'user_divisions'); // Join with mapping table

      if (userId) {
        query.where('user_divisions.user_id = :userId', { userId });
      }

      // Filter logic: specific division or default list
      if (divisionId) {
        query.where('dm.id = :divisionId', { divisionId });
      } else {
        query.where('dm.id IN (:...divisionIds)', {
          divisionIds: DIVISION_LIST, // Use constant
        });
      }

      const result = await query.getMany();

      // Cache result
      await this.cacheManager.set(cacheKey, result);

      return result.map((ud) => ({
        division_id: ud.id,
        division_name: ud.division_name,
      }));
    }
  }

  /**
   * storeTableSettings - Manages user table preferences with intelligent reset logic
   *
   * Complex Business Logic:
   * - Creates or updates table settings for a user
   * - Supports reset functionality with column preservation
   * - Intelligent cache invalidation
   * - Maintains checked column state during resets
   *
   * Reset Logic:
   * - When resetting, preserves previously checked columns
   * - If no columns were checked before, all columns become unchecked
   * - Maintains user's preference for column visibility
   *
   * @param body - Table settings data
   * @param isReset - Flag indicating reset operation
   * @returns Saved or updated table settings
   */
  async storeTableSettings(body: any, isReset = false) {
    const { user_id, setting_type, setting_pack, columns, record_per_page } =
      body;

    // Invalidate cache for this user's table settings
    await this.cacheManager.del(`table_setting:${user_id}:${setting_type}`);

    // Check for existing settings
    const existingSetting = await this.lmsTableSettingRepo.findOne({
      where: { user_id, setting_type },
    });

    // Determine setting pack: use provided or prepare from reset
    const settingPack = isReset
      ? this.prepareSettingPack(columns, existingSetting?.setting_pack)
      : setting_pack;

    // Create entity payload
    const entityPayload = plainToInstance(LmsTableSettingsEntity, {
      ...(existingSetting || {}), // Preserve existing data
      user_id,
      setting_type,
      record_per_page,
      setting_pack: settingPack,
      ...(existingSetting?.id
        ? { updated_on: new Date() } // Update timestamp
        : { created_on: new Date() }), // Creation timestamp
    });

    // Update or create
    if (existingSetting?.id) {
      await this.lmsTableSettingRepo.update(existingSetting.id, entityPayload);
      return entityPayload;
    }

    return await this.lmsTableSettingRepo.save(entityPayload);
  }

  /**
   * prepareSettingPack - Intelligent column configuration for reset operations
   *
   * Business Logic: When resetting table settings, preserve which columns
   * the user had checked/selected before the reset.
   *
   * Algorithm:
   * 1. Extract keys of previously checked columns
   * 2. For each column in new configuration:
   *    - If no columns were checked before OR this column was checked, mark as checked
   *    - Otherwise, mark as unchecked
   *
   * This preserves user's column visibility preferences during resets.
   */
  private prepareSettingPack(
    columns: Record<string, string>,
    previousPack: any,
  ): IColumnConfig[] {
    // Extract keys of previously checked columns
    const checkedKeys = previousPack
      .filter(
        (item: IColumnConfig) =>
          item.is_checked === 'true' || item.is_checked === true,
      )
      .map((item: IColumnConfig) => item.key);

    // Create new configuration preserving checked state
    return Object.entries(columns).map(([key, title]) => ({
      key,
      title,
      is_checked: String(checkedKeys.length === 0 || checkedKeys.includes(key)),
    }));
  }

  /**
   * getCountries - Retrieves countries with caching for frontend header
   *
   * Specialized caching for frontend header usage.
   * Includes timezone information for each country.
   *
   * Cache Key: 'countries_list_frontend_header' - indicates specific use case
   *
   * @returns List of countries with timezone information
   */
  async getCountries() {
    const cacheKey = `countries_list_frontend_header`;

    // Check cache
    const cachedData = await this.cacheManager.get<CountryEntity[]>(cacheKey);
    if (cachedData) {
      return cachedData;
    }

    // Query with specific fields and timezone relations
    const result = await this.countryRepo.find({
      select: ['country_id', 'name', 'country_code', 'currency_code', 'iso3'],
      relations: ['timezones'], // Eager load timezones
    });

    // Cache result
    await this.cacheManager.set(cacheKey, result);

    return result;
  }

  /**
   * updateTheme - Updates user's UI theme preference
   *
   * Business Logic: Updates or creates theme preference for a user.
   * Handles string to number conversion for user ID.
   *
   * Implementation Pattern: Upsert (Update or Insert)
   * 1. Check if theme exists for user
   * 2. Update if exists, create if not
   * 3. Return updated theme
   *
   * Note: Cache invalidation for theme changes is handled in controller
   * via Redis session update.
   */
  async updateTheme(updateThemeDto: UpdateThemeDto) {
    // Convert user ID to number if string
    const userId =
      typeof updateThemeDto.user_id === 'string'
        ? parseInt(updateThemeDto.user_id, 10)
        : updateThemeDto.user_id;

    const themeData = {
      user_id: userId,
      theme: updateThemeDto.theme,
      createdOn: new Date(),
      updatedOn: new Date(),
    };

    // Check for existing theme
    const existing = await this.themeRepo.findOne({
      where: { by_user: userId },
    });

    // Update or create
    if (existing) {
      await this.themeRepo.update(
        { by_user: userId },
        { themeColor: updateThemeDto.theme, updatedOn: new Date() },
      );
    } else {
      await this.themeRepo.save(themeData);
    }

    return this.themeRepo.findOne({ where: { by_user: userId } });
  }

  /**
   * updateMenuView - Updates user's menu display preference
   *
   * Simple update operation for menu type preference.
   * Menu types likely: 1=sidebar, 2=topbar, 3=hamburger, etc.
   *
   * @param userId - User ID
   * @param updateMenuViewDto - Menu view update data
   * @returns Updated user entity
   */
  async updateMenuView(userId: number, updateMenuViewDto: UpdateMenuViewDto) {
    await this.userRepo.update(
      { id: Number(userId) },
      {
        menu_type: Number(updateMenuViewDto.menu_type),
        modified: new Date(),
      },
    );

    return this.userRepo.findOne({ where: { id: userId } });
  }
}
