import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateDto } from './dto/create.dto';
import { plainToInstance } from 'class-transformer';
import {
  getSkipOffset,
  paginate,
} from 'src/config/pagination/pagination.service';
import { SearchDto } from './dto/search-dto';
import { UpdateDto } from './dto/update.dto';
import { Response } from 'src/config/pagination/paginationDto';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { UpdateStatusDto } from './dto/update-status.dto';
import { VcmCommissionDifferenceReasonEntity } from 'src/entities/VcmCommissionDifferenceReason.entity';

/**
 * Commission Difference Reason Service
 *
 * This service handles all business logic for managing commission difference reasons.
 * Commission difference reasons are used to categorize and explain discrepancies
 * in commission calculations within the visa commission management system.
 *
 * The service provides full CRUD operations with search, filtering, and bulk updates.
 *
 * @Injectable - Marks this class as a NestJS injectable service
 */
@Injectable()
export class CommissionDifferenceReasonService {
  constructor(
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    @InjectRepository(VcmCommissionDifferenceReasonEntity)
    private readonly diffRepo: Repository<VcmCommissionDifferenceReasonEntity>,
  ) {}

  /**
   * Search commission difference reasons with comprehensive filtering and pagination.
   *
   * Builds a complex query with multiple optional filters:
   * - Text search on reason field
   * - Filter by creator(s)
   * - Filter by last updater(s)
   * - Filter by active status
   * - Date range filters for creation and modification
   *
   * @param searchDto - Search criteria including filters and pagination
   * @returns Paginated response with commission difference reasons
   *
   * @performance Consider adding database indexes on frequently filtered columns
   * @note CacheManager is injected but not used in this method
   */
  async findAll(
    searchDto: SearchDto,
  ): Promise<Response<VcmCommissionDifferenceReasonEntity>> {
    // Start building the query with joins for user information
    const query = this.diffRepo
      .createQueryBuilder('rs')
      .leftJoin('rs.user', 'user') // Join creator user
      .addSelect(['user.fname', 'user.lname', 'user.employeeCode'])
      .leftJoin('rs.modified_by', 'modified_by') // Join last modifier user
      .addSelect([
        'modified_by.fname',
        'modified_by.lname',
        'modified_by.employeeCode',
      ])
      .orderBy('rs.id', 'DESC'); // Default order: newest first

    // Text search on reason field
    if (searchDto.search_title) {
      query.andWhere('rs.reason LIKE :search_title', {
        search_title: `%${searchDto.search_title}%`,
      });
    }

    // Filter by creator(s)
    if (searchDto.created_by && searchDto.created_by.length > 0) {
      query.andWhere('rs.by_user IN (:...created_by)', {
        created_by: searchDto.created_by,
      });
    }

    // Filter by last updater(s)
    if (searchDto.updated_by && searchDto.updated_by.length > 0) {
      query.andWhere('rs.modified_by_user IN (:...updated_by)', {
        updated_by: searchDto.updated_by,
      });
    }

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

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

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

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

    // Calculate pagination metadata
    const pagination = paginate(total, searchDto.per_page, searchDto.page);

    return { list: data, pagination };
  }

  /**
   * Get all active commission difference reasons for dropdowns/select lists.
   *
   * Returns only ID and reason fields for performance, ordered alphabetically.
   *
   * @returns Response with active commission difference reasons
   *
   * @note This method is a good candidate for caching since active reasons
   * change infrequently but are requested often for dropdowns.
   */
  async findAllActive(): Promise<
    Response<VcmCommissionDifferenceReasonEntity>
  > {
    const data = await this.diffRepo
      .createQueryBuilder('rs')
      .select(['rs.id', 'rs.reason']) // Only select necessary fields
      .where('rs.active = :active', { active: 1 })
      .orderBy('rs.reason', 'ASC') // Alphabetical order for dropdowns
      .getMany();

    return { list: data };
  }

  /**
   * Find a single commission difference reason by ID.
   *
   * @param id - Numeric ID of the commission difference reason
   * @returns Single commission difference reason entity or null
   */
  findOne(id: number) {
    return this.diffRepo.findOne({ where: { id } });
  }

  /**
   * Create a new commission difference reason.
   *
   * Validates that the reason doesn't already exist before creating.
   *
   * @param userId - ID of the user creating the reason
   * @param createDto - Commission difference reason data
   * @returns Newly created commission difference reason entity
   *
   * @throws HttpException if reason already exists or creation fails
   */
  async create(userId: number, createDto: CreateDto) {
    try {
      // Check for duplicate reason
      const exists = await this.diffRepo.findOne({
        where: { reason: createDto.reason },
      });
      if (exists) throw new Error('Difference Reason already exists.');

      // Convert DTO to entity with creator information
      const commissionResponseStatusEntity = plainToInstance(
        VcmCommissionDifferenceReasonEntity,
        {
          ...createDto,
          by_user: userId, // Set creator
        },
      );

      // Save to database
      return await this.diffRepo.save(commissionResponseStatusEntity);
    } catch (error) {
      // Handle and re-throw as HttpException
      throw new HttpException(
        error?.message || 'Something went wrong while creating',
        error?.status || HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }

  /**
   * Update an existing commission difference reason.
   *
   * Validates that the updated reason doesn't conflict with existing reasons.
   *
   * @param userId - ID of the user updating the reason
   * @param updateDto - Updated commission difference reason data
   * @returns Update result
   *
   * @throws HttpException if ID is missing, reason conflicts, or update fails
   */
  async update(userId: string, updateDto: UpdateDto) {
    const id = updateDto.id;

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

    try {
      // Check for duplicate reason (excluding current record)
      const exists = await this.diffRepo.findOne({
        where: { reason: updateDto.reason },
      });

      if (exists && exists?.id !== Number(id)) {
        throw new Error('Difference Reason already exists with another Id.');
      }

      // Convert DTO to entity with updater information
      const commissionResponseStatusEntity = plainToInstance(
        VcmCommissionDifferenceReasonEntity,
        {
          ...updateDto,
          modified_by_user: userId, // Set last modifier
        },
      );

      // Execute update
      const result = await this.diffRepo.update(
        id,
        commissionResponseStatusEntity,
      );

      // Check if record was found and 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 Commission Response Reason:', error);

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

  /**
   * Fetch users who created or updated commission difference reasons.
   *
   * Used for populating filter dropdowns in search interfaces.
   *
   * @param by - Type of users to fetch: 'created_by' or 'updated_by'
   * @returns Array of user objects with ID and formatted name
   *
   * @performance Consider caching this data since it changes infrequently
   */
  async fetchCreatedByOrUpdatedByUsers(by: string) {
    const query = this.diffRepo.createQueryBuilder('rs').select('rs.id');

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

    // Group by employee code to get distinct users
    query.groupBy('employeeCode');

    return await query.getRawMany();
  }

  /**
   * Bulk update status of commission difference reasons.
   *
   * Activates or deactivates multiple reasons at once.
   *
   * @param userId - ID of the user performing the update
   * @param updateStatusDto - Status update action and record IDs
   * @returns Update result
   *
   * @note Uses numeric values: 1 = active, 2 = deactivated (not typical 0)
   * @security Ensure user has permission to update multiple records
   */
  async updateStatus(userId: string, updateStatusDto: UpdateStatusDto) {
    const { ids, action } = updateStatusDto;

    return await this.diffRepo
      .createQueryBuilder()
      .update()
      .set({
        active: action === 'activate' ? 1 : 2, // Note: 2 instead of 0 for deactivated
        modified_by_user: Number(userId),
      })
      .where('id IN (:...ids)', { ids })
      .execute();
  }
}
