// 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 multiple entity definitions
import { VisaServiceMasterEntity } from 'src/entities/VisaServiceMaster.entity';
import { CollegeEntity } from 'src/entities/College.entity';
import { SearchAssociatesDto } from './dto/search-associates-dto';
import { MasteragentEntity } from 'src/entities/Masteragent.entity';
import {
  getSkipOffset,
  paginate,
} from 'src/config/pagination/pagination.service';
import { Response } from 'src/config/pagination/paginationDto';
import { FileService } from 'src/services/file.service';
import { VcmCommissionBonusReceivedEntity } from 'src/entities/VcmCommissionBonusReceived.entity';
import { SearchBonusReceivedDto } from './dto/search-dto';
import { AddAttachmentDto } from './dto/add-attachment.dto';
import { plainToInstance } from 'class-transformer';
import { RemoveAttachmentDto } from './dto/remove-attachment.dto';
import { CompanyBankAccountEntity } from 'src/entities/CompanyBankAccount.entity';
import { PaymentModeMasterEntity } from 'src/entities/PaymentModeMaster.entity';
import { CreateBonusReceivedDto } from './dto/create-bonus-received.dto';
import { formatDate } from 'src/utils/common/global-utility';
import { UserDataProps } from 'src/utils/decorators/user-data.decorator';

/**
 * VisaBonusReceivedService - Service layer for visa bonus receipt management
 *
 * This service handles complex bonus receipt tracking with dual-association support
 * (institutes and master agents). It implements sophisticated query patterns,
 * file management, and financial transaction processing.
 *
 * Business Domain: Visa Commission & Bonus Receipt Tracking
 * - Tracks bonuses received from educational institutes (type 1)
 * - Tracks bonuses received from master agents (type 2)
 * - Manages financial transactions with tax details
 * - Handles file attachments for transaction proof
 * - Supports comprehensive filtering and reporting
 *
 * Key Complexity Factors:
 * - Dual-association architecture with different entity relationships
 * - Complex multi-entity joins with conditional logic
 * - Transaction management for financial data integrity
 * - File attachment management with physical file handling
 */
@Injectable()
export class VisaBonusReceivedService {
  // Logger instance for service-specific logging
  private logger = new Logger(VisaBonusReceivedService.name);

  /**
   * Constructor with dependency injection for 6 repositories
   *
   * @param dataSource - TypeORM DataSource for transaction management
   * @param fileService - Shared service for file operations
   * @param cacheManager - Cache manager (injected but not utilized - optimization opportunity)
   * @param bonusRepo - Main bonus receipt transaction repository
   * @param vsmRepo - Visa service master repository
   * @param companyBankRepo - Company bank account repository
   * @param payModeRepo - Payment mode repository
   * @param collegeRepo - Educational institute repository
   * @param masterAgentRepo - Master agent repository
   */
  constructor(
    private dataSource: DataSource,
    private fileService: FileService,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    @InjectRepository(VcmCommissionBonusReceivedEntity)
    private readonly bonusRepo: Repository<VcmCommissionBonusReceivedEntity>,
    @InjectRepository(VisaServiceMasterEntity)
    private readonly vsmRepo: Repository<VisaServiceMasterEntity>,
    @InjectRepository(CompanyBankAccountEntity)
    private readonly companyBankRepo: Repository<CompanyBankAccountEntity>,
    @InjectRepository(PaymentModeMasterEntity)
    private readonly payModeRepo: Repository<PaymentModeMasterEntity>,
    @InjectRepository(CollegeEntity)
    private readonly collegeRepo: Repository<CollegeEntity>,
    @InjectRepository(MasteragentEntity)
    private readonly masterAgentRepo: Repository<MasteragentEntity>,
  ) {}

  /**
   * findAll - Retrieves paginated bonus receipts with dual-association conditional joins
   *
   * Complex query with conditional join mapping based on association type:
   * - Association type 1 (institute): Join with CollegeEntity
   * - Association type 2 (master agent): Join with MasteragentEntity
   *
   * Uses leftJoinAndMapOne for conditional entity mapping, which is a
   * sophisticated TypeORM pattern for polymorphic relationships.
   *
   * @param dto - Search parameters including association_type
   * @returns Paginated response with bonus receipts and related data
   */
  async findAll(
    dto: SearchBonusReceivedDto,
  ): Promise<Response<VcmCommissionBonusReceivedEntity>> {
    const query = this.bonusRepo.createQueryBuilder('main');

    // Conditional join mapping based on association type
    if (dto.association_type === '1') {
      // Institute association: map to CollegeEntity
      query.leftJoinAndMapOne(
        'main.institute',
        CollegeEntity,
        'institute',
        'institute.collegeId = main.associate_id',
      );
    } else if (dto.association_type === '2') {
      // Master agent association: map to MasteragentEntity
      query.leftJoinAndMapOne(
        'main.associate',
        MasteragentEntity,
        'associate',
        'associate.id = main.associate_id',
      );
    }

    // Common joins for all association types
    query
      .leftJoin('main.visa_country', 'country')
      .addSelect('country.name', 'country_name')
      .leftJoin('main.bank', 'bank')
      .addSelect([
        'bank.bank_name',
        'bank.acc_number',
        'bank.company_bank_address',
      ])
      .leftJoin('main.mode', 'mode')
      .addSelect(['mode.payment_mode_name'])
      .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',
      ])
      .where('main.association_type = :association_type', {
        association_type: dto.association_type,
      })
      .orderBy('main.id', 'DESC');

    this.filterWhereConditions(query, dto);
    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 with multiple criteria
   *
   * Supports multi-select filters, date ranges, and user-based filtering.
   * Note: search_title filter is defined but empty - needs implementation.
   *
   * @param query - TypeORM query builder
   * @param dto - Search DTO with filter parameters
   */
  private filterWhereConditions(
    query: SelectQueryBuilder<VcmCommissionBonusReceivedEntity>,
    dto: SearchBonusReceivedDto,
  ) {
    // Note: search_title filter is empty - needs implementation
    if (dto.search_title) {
      // TODO: Implement text search logic
    }

    // Multi-select filter for visa service countries
    if (dto.visa_service_country_id && dto.visa_service_country_id.length > 0) {
      query.andWhere('main.visa_service_country_id IN (:...visa_country)', {
        visa_country: dto.visa_service_country_id,
      });
    }

    // Multi-select filter for associates (institutes or agents)
    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 bank accounts
    if (dto.bank_account_id && dto.bank_account_id.length > 0) {
      query.andWhere('main.bank_account_id IN (:...bank_account_id)', {
        bank_account_id: dto.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,
        });
      }
    }
  }

  /**
   * getVisaCountryList - Retrieves countries for study visa services
   *
   * Hardcoded product_id 8 likely represents "Study Visa" product.
   *
   * @returns List of countries where study visa services are active
   */
  async getVisaCountryList(): Promise<any> {
    return await this.vsmRepo
      .createQueryBuilder('vsm')
      .innerJoin('vsm.country', 'country')
      .select([
        'country.country_id AS id',
        'country.iso3 AS iso3',
        'country.name AS name',
        'country.country_code AS country_code',
      ])
      .where('vsm.active = :active', { active: '1' })
      .andWhere('vsm.visa_type = :productId', { productId: 8 }) // Study Visa
      .groupBy('country.country_id')
      .getRawMany();
  }

  /**
   * getAssociateList - Retrieves associates with type-based logic
   *
   * Implements strategy pattern for different association types:
   * - Type 1: Fetch educational institutes
   * - Type 2: Fetch master agents
   *
   * @param params - Search parameters including association_type
   * @returns Filtered list of associates based on type
   */
  async getAssociateList(params: SearchAssociatesDto): Promise<any> {
    switch (params.association_type) {
      case 1:
        return await this.fetchInstitutes(params);
      case 2:
        return await this.fetchMasterAgents(params);
      default:
        return [];
    }
  }

  /**
   * fetchInstitutes - Retrieves educational institutes with country filtering
   *
   * @param params - Search parameters including visa_service_country_id
   * @returns Filtered list of institutes
   */
  private async fetchInstitutes(params: SearchAssociatesDto): Promise<any> {
    const qb = this.collegeRepo
      .createQueryBuilder('college')
      .innerJoin('college.campus', 'campus')
      .select([
        'college.collegeId AS id',
        'CONCAT(college.institueName, " (", college.UID, ")") AS name',
      ]);
    if (params.visa_service_country_id) {
      qb.where('campus.country_id = :country_id', {
        country_id: params.visa_service_country_id,
      });
    }
    qb.groupBy('college.collegeId').orderBy('college.institueName', 'ASC');
    return await qb.getRawMany();
  }

  /**
   * fetchMasterAgents - Retrieves master agents for study visa product
   *
   * Filters agents that have product_id 8 (Study Visa) in their products.
   *
   * @param params - Search parameters
   * @returns Filtered list of master agents
   */
  private async fetchMasterAgents(params: SearchAssociatesDto): Promise<any> {
    return await this.masterAgentRepo
      .createQueryBuilder('agent')
      .innerJoin('agent.products', 'products')
      .select([
        'agent.id AS id',
        'CONCAT(agent.masteragentName, " (", agent.uid, ")") AS name',
      ])
      .where('products.masteragentProductIds = :product_id', {
        product_id: 8, // Study Visa
      })
      .groupBy('agent.id')
      .orderBy('agent.masteragentName', 'ASC')
      .getRawMany();
  }

  /**
   * getPaymentModeList - Retrieves active 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
   */
  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();
  }

  // ==================== FETCH BY LIST METHODS WITH TYPE FILTERING ====================
  // These methods return distinct values from existing bonus records
  // Filtered by association type for context-specific dropdowns

  /**
   * fetchServiceCountriesByList - Distinct service countries by association type
   *
   * @param association_type - Filter by association type (1=institute, 2=agent)
   * @returns Distinct service countries for the given association type
   */
  async fetchServiceCountriesByList(association_type: number) {
    return await this.bonusRepo
      .createQueryBuilder('vcm')
      .leftJoin('vcm.visa_country', 'country')
      .select([
        'DISTINCT vcm.visa_service_country_id AS id',
        'country.name AS name',
      ])
      .where('vcm.association_type = :association_type', { association_type })
      .orderBy('country.name', 'ASC')
      .getRawMany();
  }

  /**
   * fetchAssociatesByList - Distinct associates by association type
   *
   * Complex conditional logic with different joins based on association type.
   *
   * @param association_type - Determines which entity to join with
   * @returns Distinct associates for the given association type
   */
  async fetchAssociatesByList(association_type: number) {
    const qb = this.bonusRepo.createQueryBuilder('vcm');
    switch (association_type) {
      case 1: // institute
        return await qb
          .innerJoin(
            'college',
            'college',
            'college.collegeId = vcm.associate_id',
          )
          .select([
            'college.collegeId AS id',
            `CONCAT(college.institueName, " (", college.UID, ")") AS name`,
          ])
          .where('vcm.association_type = :association_type', {
            association_type,
          })
          .groupBy('college.collegeId')
          .orderBy('college.institueName', 'ASC')
          .getRawMany();
      case 2: // master agent
        return await qb
          .innerJoin('masteragent', 'ma', 'ma.id = vcm.associate_id')
          .select([
            'ma.id AS id',
            `CONCAT(ma.masteragentName, " (", ma.uid, ")") AS name`,
          ])
          .where('vcm.association_type = :association_type', {
            association_type,
          })
          .groupBy('ma.id')
          .orderBy('ma.masteragentName', 'ASC')
          .getRawMany();
    }
  }

  /**
   * fetchPaymentModesByList - Distinct payment modes by association type
   */
  async fetchPaymentModesByList(association_type: number) {
    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');
    if (association_type) {
      query.andWhere('main.association_type = :association_type', {
        association_type,
      });
    }
    return await query.getRawMany();
  }

  /**
   * fetchReceivedBankAccountsByList - Distinct bank accounts by association type
   */
  async fetchReceivedBankAccountsByList(association_type: number) {
    const query = this.bonusRepo.createQueryBuilder('main');
    query
      .innerJoin('main.bank', 'bank')
      .select([
        'main.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.bank_account_id');
    if (association_type) {
      query.andWhere('main.association_type = :association_type', {
        association_type,
      });
    }
    return await query.getRawMany();
  }

  /**
   * fetchCreatedByOrUpdatedByUsers - Distinct users with type filtering
   *
   * @param by - Filter type: 'created_by' or 'updated_by'
   * @param association_type - Association type filter
   * @returns Distinct users with their activity
   */
  async fetchCreatedByOrUpdatedByUsers(by: string, association_type: number) {
    const query = this.bonusRepo
      .createQueryBuilder('vcm')
      .where('vcm.association_type = :association_type', { association_type });

    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 bonus receipt
   */
  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 file attachment with physical file deletion
   *
   * Business logic:
   * 1. Find the bonus receipt 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;

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

    if (!record?.attachment_file?.length) return params;

    const updatedAttachments = record.attachment_file.filter(
      (file: string) => file !== params.file_name,
    );

    if (updatedAttachments.length !== record.attachment_file.length) {
      const entity = plainToInstance(VcmCommissionBonusReceivedEntity, {
        attachment_file: updatedAttachments,
        modified_by_user: userData.id,
      });

      await repository.update({ id: params.row_id }, entity);
      await this.fileService.removeFile(params.file_name);
    }

    return params;
  }

  /**
   * addAttachments - Adds new file attachments to an existing bonus receipt
   *
   * @param params - Contains bonus receipt 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;

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

    const updatedAttachments = record.attachment_file
      ? [...record.attachment_file, ...params.attachment_file]
      : params.attachment_file;

    if (updatedAttachments.length) {
      const entity = plainToInstance(VcmCommissionBonusReceivedEntity, {
        attachment_file: updatedAttachments,
        modified_by_user: userData.id,
      });

      await repository.update({ id: params.id }, entity);
    }

    return params;
  }

  /**
   * create - Creates a new bonus receipt with transaction support
   *
   * Business logic:
   * - Formats payment date to YYYY-MM-DD
   * - Parses tax details JSON if provided
   * - Handles optional bank account ID
   * - Tracks creator user
   *
   * @param createDto - Bonus receipt creation data
   * @param userId - ID of user creating the receipt
   * @returns Created bonus receipt entity
   */
  async create(createDto: CreateBonusReceivedDto, userId: string) {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.startTransaction();

    try {
      const bonusEntity = plainToInstance(VcmCommissionBonusReceivedEntity, {
        ...createDto,
        payment_date: formatDate(createDto.payment_date, 'YYYY-MM-DD'),
        tax_details: createDto.tax_details
          ? JSON.parse(createDto.tax_details as any)
          : null,
        bank_account_id: createDto.bank_account_id ?? null,
        by_user: userId,
      });

      const bonus = await queryRunner.manager.save(bonusEntity);

      await queryRunner.commitTransaction();
      return bonus;
    } catch (error) {
      await queryRunner.rollbackTransaction();
      this.logger.error('Create visa bonus received error', error?.stack);
      throw new InternalServerErrorException(
        'Failed to create visa bonus received',
      );
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * update - Updates an existing bonus receipt with transaction support
   *
   * @param updateDto - Bonus receipt update data (includes ID)
   * @param userId - ID of user updating the receipt
   * @returns Update result
   */
  async update(updateDto: CreateBonusReceivedDto, 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 {
      const bonusEntity = plainToInstance(VcmCommissionBonusReceivedEntity, {
        ...updateDto,
        payment_date: formatDate(updateDto.payment_date, 'YYYY-MM-DD'),
        tax_details: updateDto.tax_details
          ? JSON.parse(updateDto.tax_details as any)
          : null,
        modified_by_user: userId,
      });

      const bonus = await queryRunner.manager.update(
        VcmCommissionBonusReceivedEntity,
        { id },
        bonusEntity,
      );

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