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

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

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

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

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

// Import shared file service for file operations
import { FileService } from 'src/services/file.service';

// Import DTOs for request validation
import { SearchBonusPaidDto } from './dto/search-dto';
import { VcmSubAgentBonusPaidEntity } from 'src/entities/VcmSubAgentBonusPaid.entity';
import { SearchAssociateBanksDto } from './dto/search-associate-banks-dto';
import { B2bAffiliateAccountBanksEntity } from 'src/entities/B2bAffiliateAccountBanks.entity';
import { CompanyBankAccountEntity } from 'src/entities/CompanyBankAccount.entity';
import { B2bAffiliateAccountsEntity } from 'src/entities/B2bAffiliateAccounts.entity';
import { PaymentModeMasterEntity } from 'src/entities/PaymentModeMaster.entity';
import { CreateBonusPaidDto } from './dto/create-bonus-paid.dto';

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

// Import utility functions
import { formatDate } from 'src/utils/common/global-utility';
import { RemoveAttachmentDto } from './dto/remove-attachment.dto';
import { AddAttachmentDto } from './dto/add-attachment.dto';
import { UserDataProps } from 'src/utils/decorators/user-data.decorator';

/**
 * SubAgentBonusPaidService - Service layer for sub-agent bonus payment business logic
 *
 * This service handles all data operations for bonus payment management including:
 * - Complex financial transaction processing
 * - Multi-entity joins and filtering
 * - File attachment management
 * - Audit trail and transaction logging
 * - Reference data management for dropdowns
 *
 * Business Domain: Financial Commission Management
 * - Processes bonus payments to sub-agents/affiliates
 * - Manages bank account information for both parties
 * - Tracks payment modes and tax details
 * - Handles file attachments for transaction proof
 *
 * Key Features:
 * - Transaction management with rollback support
 * - Comprehensive filtering and search capabilities
 * - File upload and management
 * - User activity tracking
 * - Formatted reference data for UI dropdowns
 */
@Injectable()
export class SubAgentBonusPaidService {
  // Logger instance for service-specific logging
  private logger = new Logger(SubAgentBonusPaidService.name);

  /**
   * Constructor with extensive dependency injection
   *
   * @param dataSource - TypeORM DataSource for transaction management
   * @param fileService - Shared service for file operations
   * @param cacheManager - Cache manager (injected but not used in current implementation)
   * @param bonusRepo - Repository for main bonus payment transactions
   * @param associateBankRepo - Repository for affiliate bank accounts
   * @param companyBankRepo - Repository for company bank accounts
   * @param associateRepo - Repository for affiliate master data
   * @param payModeRepo - Repository for payment mode definitions
   */
  constructor(
    private dataSource: DataSource,
    private fileService: FileService,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    @InjectRepository(VcmSubAgentBonusPaidEntity)
    private readonly bonusRepo: Repository<VcmSubAgentBonusPaidEntity>,
    @InjectRepository(B2bAffiliateAccountBanksEntity)
    private readonly associateBankRepo: Repository<B2bAffiliateAccountBanksEntity>,
    @InjectRepository(CompanyBankAccountEntity)
    private readonly companyBankRepo: Repository<CompanyBankAccountEntity>,
    @InjectRepository(B2bAffiliateAccountsEntity)
    private readonly associateRepo: Repository<B2bAffiliateAccountsEntity>,
    @InjectRepository(PaymentModeMasterEntity)
    private readonly payModeRepo: Repository<PaymentModeMasterEntity>,
  ) {}

  /**
   * findAll - Retrieves paginated bonus payments with comprehensive filtering
   *
   * Builds a complex query with multiple joins for complete transaction view:
   * - Joins with affiliate information
   * - Joins with payment mode
   * - Joins with both from/to bank accounts
   * - Joins with creator and updater user information
   *
   * @param dto - Search and filter parameters
   * @returns Paginated response with bonus payments and related data
   */
  async findAll(
    dto: SearchBonusPaidDto,
  ): Promise<Response<VcmSubAgentBonusPaidEntity>> {
    const query = this.bonusRepo.createQueryBuilder('main');

    // Complex join structure for complete transaction view
    query
      .leftJoin('main.associate', 'associate')
      .addSelect(['associate.affiliate_name', 'associate.uid'])
      .leftJoin('main.mode', 'mode')
      .addSelect(['mode.payment_mode_name'])
      .leftJoin('main.from_bank', 'from_bank')
      .addSelect([
        'from_bank.bank_name',
        'from_bank.acc_number',
        'from_bank.company_bank_address',
      ])
      .leftJoin('main.to_bank', 'to_bank')
      .addSelect([
        'to_bank.bank_name',
        'to_bank.acc_number',
        'to_bank.branch_address',
      ])
      .leftJoin('main.user', 'user')
      .addSelect(['user.fname', 'user.lname', 'user.employeeCode'])
      .leftJoin('main.modified_by', 'modified_by')
      .addSelect([
        'modified_by.fname',
        'modified_by.lname',
        'modified_by.employeeCode',
      ])
      .orderBy('main.id', 'DESC');

    // Apply filters
    this.filterWhereConditions(query, dto);

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

    return { list: data, pagination };
  }

  /**
   * filterWhereConditions - Applies filtering conditions to query builder
   *
   * Private helper method that encapsulates all filter logic.
   * Supports multi-select filters, date ranges, and user filters.
   *
   * @param query - TypeORM query builder instance
   * @param dto - Search DTO containing filter parameters
   */
  private filterWhereConditions(
    query: SelectQueryBuilder<VcmSubAgentBonusPaidEntity>,
    dto: SearchBonusPaidDto,
  ) {
    // Note: search_title filter is defined but empty - needs implementation
    if (dto.search_title) {
      // TODO: Implement text search logic
    }

    // Multi-select filter for associate IDs
    if (dto.associate_id && dto.associate_id.length > 0) {
      query.andWhere('main.associate_id IN (:...associate_id)', {
        associate_id: dto.associate_id,
      });
    }

    // Multi-select filter for payment modes
    if (dto.payment_mode_id && dto.payment_mode_id.length > 0) {
      query.andWhere('main.payment_mode_id IN (:...payment_mode_id)', {
        payment_mode_id: dto.payment_mode_id,
      });
    }

    // Multi-select filter for source bank accounts
    if (dto.from_bank_account_id && dto.from_bank_account_id.length > 0) {
      query.andWhere(
        'main.from_bank_account_id IN (:...from_bank_account_id)',
        {
          from_bank_account_id: dto.from_bank_account_id,
        },
      );
    }

    // Multi-select filter for destination bank accounts
    if (dto.to_bank_account_id && dto.to_bank_account_id.length > 0) {
      query.andWhere('main.to_bank_account_id IN (:...to_bank_account_id)', {
        to_bank_account_id: dto.to_bank_account_id,
      });
    }

    // Filter by creators
    if (dto.created_by && dto.created_by.length > 0) {
      query.andWhere('main.by_user IN (:...created_by)', {
        created_by: dto.created_by,
      });
    }

    // Filter by updaters
    if (dto.updated_by && dto.updated_by.length > 0) {
      query.andWhere('main.modified_by_user IN (:...updated_by)', {
        updated_by: dto.updated_by,
      });
    }

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

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

  /**
   * getAssociateList - Retrieves active affiliates for dropdowns
   *
   * @returns Formatted list of affiliates with ID and display name
   */
  async getAssociateList(): Promise<any> {
    return await this.associateRepo
      .createQueryBuilder('agent')
      .select([
        'agent.id AS id',
        `CONCAT(agent.affiliate_name, " (", agent.uid, ")") AS name`,
      ])
      .where('agent.status = :status', { status: 1 })
      .orderBy('agent.affiliate_name', 'ASC')
      .getRawMany();
  }

  /**
   * getPaymentModeList - Retrieves active payment modes for dropdowns
   *
   * @returns Formatted list of payment modes
   */
  async getPaymentModeList(): Promise<any> {
    return await this.payModeRepo
      .createQueryBuilder('pay')
      .select(['pay.payment_mode_id AS id', 'pay.payment_mode_name AS name'])
      .where('pay.active = :active', { active: 1 })
      .orderBy('pay.payment_mode_name', 'ASC')
      .getRawMany();
  }

  /**
   * getCompanyBankList - Retrieves active company bank accounts for dropdowns
   *
   * @returns Formatted list of company bank accounts
   */
  async getCompanyBankList(): Promise<any> {
    return await this.companyBankRepo
      .createQueryBuilder('cb')
      .select([
        'cb.bank_acc_id AS id',
        `CONCAT(cb.acc_number, ' - ', cb.bank_name, ', ', cb.company_bank_address) AS name`,
      ])
      .andWhere('cb.active = :active', {
        active: 1,
      })
      .getRawMany();
  }

  /**
   * getAssociateBankList - Retrieves bank accounts for specific affiliate
   *
   * @param params - Contains associate ID to filter bank accounts
   * @returns Formatted list of affiliate bank accounts
   */
  async getAssociateBankList(params: SearchAssociateBanksDto): Promise<any> {
    return await this.associateBankRepo
      .createQueryBuilder('ab')
      .select([
        'ab.id AS id',
        `CONCAT(ab.acc_number, ' - ', ab.bank_name, ', ', ab.branch_address) AS name`,
      ])
      .where('ab.b2b_affiliate_account_id = :associate_id', {
        associate_id: params.associate_id,
      })
      .andWhere('ab.status = :status', {
        status: 1,
      })
      .getRawMany();
  }

  /**
   * create - Creates a new bonus payment with transaction support
   *
   * Uses transaction to ensure data consistency.
   * Handles complex data transformations:
   * - JSON parsing for tax details
   * - Date formatting for payment date
   * - Null handling for optional bank accounts
   *
   * @param createDto - Bonus payment creation data
   * @param userId - ID of user creating the payment
   * @returns Created bonus payment entity
   */
  async create(createDto: CreateBonusPaidDto, userId: string) {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.startTransaction();

    try {
      // Transform DTO to entity with proper data formatting
      const bonusEntity = plainToInstance(VcmSubAgentBonusPaidEntity, {
        ...createDto,
        tax_details: createDto.tax_details
          ? JSON.parse(createDto.tax_details as any)
          : null,
        payment_date: formatDate(createDto.payment_date, 'YYYY-MM-DD'),
        from_bank_account_id:
          createDto.from_bank_account_id === ''
            ? null
            : createDto.from_bank_account_id,
        to_bank_account_id: !createDto.to_bank_account_id
          ? null
          : createDto.to_bank_account_id,
        by_user: userId, // Track creator
      });

      // Save entity within transaction
      const bonus = await queryRunner.manager.save(bonusEntity);

      // Commit transaction
      await queryRunner.commitTransaction();
      return bonus;
    } catch (error) {
      // Rollback on error with logging
      await queryRunner.rollbackTransaction();
      this.logger.error('Create sub agent bonus error', error?.stack);
      throw new InternalServerErrorException(
        'Failed to create sub agent bonus',
      );
    } finally {
      // Always release query runner
      await queryRunner.release();
    }
  }

  /**
   * update - Updates an existing bonus payment with transaction support
   *
   * Similar to create() but for updates, tracks modifier user.
   *
   * @param updateDto - Bonus payment update data (includes ID)
   * @param userId - ID of user updating the payment
   * @returns Updated bonus payment entity
   */
  async update(updateDto: CreateBonusPaidDto, userId: string) {
    const id = updateDto.id;
    if (!id) throw new HttpException('Id is required', HttpStatus.BAD_REQUEST);

    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.startTransaction();

    try {
      // Transform DTO to entity with proper data formatting
      const bonusEntity = plainToInstance(VcmSubAgentBonusPaidEntity, {
        ...updateDto,
        tax_details: updateDto.tax_details
          ? JSON.parse(updateDto.tax_details as any)
          : null,
        payment_date: formatDate(updateDto.payment_date, 'YYYY-MM-DD'),
        from_bank_account_id:
          updateDto.from_bank_account_id === ''
            ? null
            : updateDto.from_bank_account_id,
        to_bank_account_id: !updateDto.to_bank_account_id
          ? null
          : updateDto.to_bank_account_id,
        modified_by_user: userId, // Track updater
      });

      // Update entity within transaction
      const bonus = await queryRunner.manager.update(
        VcmSubAgentBonusPaidEntity,
        { id },
        bonusEntity,
      );

      await queryRunner.commitTransaction();
      return bonusEntity;
    } catch (error) {
      await queryRunner.rollbackTransaction();
      this.logger.error('Update sub agent bonus error', error?.stack);
      throw new InternalServerErrorException(
        'Failed to update sub agent bonus',
      );
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * fetchAssociatesByList - Retrieves distinct affiliates from bonus payments
   *
   * Different from getAssociateList() - this only returns affiliates
   * that have existing bonus payments.
   *
   * @returns Formatted list of affiliates with existing payments
   */
  async fetchAssociatesByList() {
    const query = this.bonusRepo.createQueryBuilder('main');

    query
      .leftJoin('main.associate', 'associate')
      .select([
        'associate.id AS id',
        `CONCAT(associate.affiliate_name, " (", associate.uid, ")") AS name`,
      ])
      .orderBy('associate.affiliate_name', 'ASC')
      .groupBy('main.associate_id'); // Distinct by associate

    return await query.getRawMany();
  }

  /**
   * fetchPaymentModesByList - Retrieves distinct payment modes from bonus payments
   *
   * @returns Formatted list of payment modes used in existing payments
   */
  async fetchPaymentModesByList() {
    const query = this.bonusRepo.createQueryBuilder('main');
    query
      .innerJoin('main.mode', 'mode')
      .select(['main.payment_mode_id AS id', 'mode.payment_mode_name AS name'])
      .orderBy('mode.payment_mode_name', 'ASC')
      .groupBy('main.payment_mode_id'); // Distinct by payment mode
    return await query.getRawMany();
  }

  /**
   * fetchFromBankAccountsByList - Retrieves distinct source bank accounts
   *
   * @returns Formatted list of company bank accounts used as sources
   */
  async fetchFromBankAccountsByList() {
    const query = this.bonusRepo.createQueryBuilder('main');
    query
      .innerJoin('main.from_bank', 'bank')
      .select([
        'main.from_bank_account_id AS id',
        `CONCAT(bank.acc_number, " - ", bank.bank_name, ", ",bank.company_bank_address) AS name`,
      ])
      .orderBy('bank.bank_name', 'ASC')
      .groupBy('main.from_bank_account_id'); // Distinct by source bank

    return await query.getRawMany();
  }

  /**
   * fetchToBankAccountsByList - Retrieves distinct destination bank accounts
   *
   * @returns Formatted list of affiliate bank accounts used as destinations
   */
  async fetchToBankAccountsByList() {
    const query = this.bonusRepo.createQueryBuilder('main');
    query
      .innerJoin('main.to_bank', 'bank')
      .select([
        'main.to_bank_account_id AS id',
        `CONCAT(bank.acc_number, " - ", bank.bank_name, ", ",bank.branch_address) AS name`,
      ])
      .orderBy('bank.bank_name', 'ASC')
      .groupBy('main.to_bank_account_id'); // Distinct by destination bank

    return await query.getRawMany();
  }

  /**
   * fetchCreatedByOrUpdatedByUsers - Retrieves users involved in bonus payments
   *
   * @param by - Filter type: 'created_by' or 'updated_by'
   * @returns Formatted list of users with their activity
   */
  async fetchCreatedByOrUpdatedByUsers(by: string) {
    const query = this.bonusRepo.createQueryBuilder('vcm');

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

    if (by === 'updated_by') {
      query
        .leftJoin('vcm.modified_by', 'modified_by')
        .select('modified_by.id', 'id')
        .addSelect(
          `CONCAT(modified_by.fname, ' ', modified_by.lname, ' - ', modified_by.employeeCode)`,
          'name',
        )
        .andWhere('vcm.modified_by IS NOT NULL')
        .orderBy('modified_by.fname', 'ASC')
        .addOrderBy('modified_by.lname', 'ASC')
        .groupBy('modified_by.id');
    }

    return await query.getRawMany();
  }

  /**
   * getAttachments - Retrieves file attachments for a specific bonus payment
   *
   * @param id - Bonus payment ID
   * @returns Bonus payment entity with attachment files
   */
  async getAttachments(id: string): Promise<any> {
    return await this.bonusRepo
      .createQueryBuilder('main')
      .select(['main.id', 'main.attachment_file'])
      .where('main.id = :id', { id })
      .orderBy('main.id', 'DESC')
      .getOne();
  }

  /**
   * removeAttachments - Removes a specific file attachment from a bonus payment
   *
   * Business logic:
   * 1. Find the bonus payment record
   * 2. Filter out the specified file from attachment array
   * 3. Update record with modified attachment list
   * 4. Physically remove file from storage
   *
   * @param params - Contains row ID and file name to remove
   * @param userData - User data for audit trail
   * @returns Original parameters (for confirmation)
   */
  async removeAttachments(
    params: RemoveAttachmentDto,
    userData: UserDataProps,
  ): Promise<any> {
    let repository;
    repository = this.bonusRepo;

    // Find the record
    const record = await repository
      .createQueryBuilder()
      .where('id = :id', { id: params.row_id })
      .getOne();

    // If no attachments, return early
    if (!record?.attachment_file?.length) return params;

    // Filter out the specified file
    const updatedAttachments = record.attachment_file.filter(
      (file: string) => file !== params.file_name,
    );

    // Only update if file was actually removed
    if (updatedAttachments.length !== record.attachment_file.length) {
      const entity = plainToInstance(VcmSubAgentBonusPaidEntity, {
        attachment_file: updatedAttachments,
        modified_by_user: userData.id ?? '1', // Default to user ID 1 if not provided
      });

      // Update database record
      await repository.update({ id: params.row_id }, entity);

      // Remove physical file from storage
      await this.fileService.removeFile(params.file_name);
    }

    return params;
  }

  /**
   * addAttachments - Adds new file attachments to an existing bonus payment
   *
   * Business logic:
   * 1. Find the bonus payment record
   * 2. Merge existing attachments with new attachments
   * 3. Update record with combined attachment list
   *
   * @param params - Contains bonus payment ID and new file attachments
   * @param userData - User data for audit trail
   * @returns Original parameters (for confirmation)
   */
  async addAttachments(
    params: AddAttachmentDto,
    userData: UserDataProps,
  ): Promise<any> {
    let repository;
    repository = this.bonusRepo;

    // Find the record
    const record = await repository
      .createQueryBuilder()
      .where('id = :id', { id: params.id })
      .getOne();

    // Merge existing and new attachments
    const updatedAttachments = record.attachment_file
      ? [...record.attachment_file, ...params.attachment_file]
      : params.attachment_file;

    // Update if there are attachments
    if (updatedAttachments.length) {
      const entity = plainToInstance(VcmSubAgentBonusPaidEntity, {
        attachment_file: updatedAttachments,
        modified_by_user: userData.id ?? '1', // Default to user ID 1 if not provided
      });

      // Update database record
      await repository.update({ id: params.id }, entity);
    }

    return params;
  }
}
