import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
import type { FindOptionsWhere } from 'typeorm';
import { BankAccountRepository } from './repositories/bank-account.repository';
import { BankRepository } from '../bank/repositories/bank.repository';
import { CreateBankAccountDto, UpdateBankAccountDto, ListBankAccountsDto } from './dto';
import { BankAccountEntity } from '../../entities/bank-account.entity';
import { serviceMessages } from '../../common/constants/messages';

const MSG = serviceMessages('Bank account');

@Injectable()
export class BankAccountService {
  constructor(
    private readonly repo: BankAccountRepository,
    private readonly bankRepo: BankRepository,
  ) {}

  async findAll(query?: ListBankAccountsDto) {
    const where: FindOptionsWhere<BankAccountEntity> = {};
    if (query?.bank_id) where.bank_id = query.bank_id;

    return this.repo.findPaginated({
      page: query?.page,
      limit: query?.limit,
      search: query?.search,
      searchColumns: ['account_name', 'cin', 'pan', 'gst'],
      relations: ['bank'],
      order: { account_name: 'ASC' },
      where,
    });
  }

  async findAllActive() {
    return this.repo.findAllActive();
  }

  async findAllForLookup() {
    const items = await this.repo.find({
      where: { is_active: true },
      relations: { bank: true },
      order: { account_name: 'ASC' },
    });

    return items
      .filter((item) => item.bank && !item.bank.is_deleted && item.bank.is_active)
      .map((item) => ({
        id: item.id,
        account_name: item.account_name,
        bank_id: item.bank_id,
        bank_name: item.bank.name,
      }));
  }

  async findById(id: string) {
    const item = await this.repo.findByIdWithBank(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);
    return item;
  }

  async create(dto: CreateBankAccountDto) {
    await this.assertBankUsable(dto.bank_id);

    const item = await this.repo.save({
      bank_id: dto.bank_id,
      account_name: dto.account_name.trim(),
      cin: dto.cin ?? null,
      pan: dto.pan ?? null,
      gst: dto.gst ?? null,
      is_active: dto.is_active ?? true,
    });

    return { id: item.id, message: MSG.CREATED };
  }

  async update(id: string, dto: UpdateBankAccountDto) {
    const item = await this.findById(id);

    if (dto.bank_id && dto.bank_id !== item.bank_id) {
      await this.assertBankUsable(dto.bank_id);
    }

    const updateData: Partial<BankAccountEntity> = {};
    if (dto.bank_id !== undefined) updateData.bank_id = dto.bank_id;
    if (dto.account_name !== undefined) updateData.account_name = dto.account_name.trim();
    if (dto.cin !== undefined) updateData.cin = dto.cin;
    if (dto.pan !== undefined) updateData.pan = dto.pan;
    if (dto.gst !== undefined) updateData.gst = dto.gst;
    if (dto.is_active !== undefined) updateData.is_active = dto.is_active;

    await this.repo.update(id, updateData);
    return { id, message: MSG.UPDATED };
  }

  async remove(id: string) {
    await this.findById(id);
    await this.repo.softDelete(id);
    return { message: MSG.DELETED };
  }

  /**
   * Collect Payment / MD stamp — active account on an active bank, tenant-scoped.
   */
  async requireActive(id: string): Promise<BankAccountEntity> {
    const item = await this.findById(id);
    if (!item.is_active) {
      throw new BadRequestException('Bank account is inactive');
    }
    if (!item.bank || item.bank.is_deleted || !item.bank.is_active) {
      throw new BadRequestException('Bank is inactive');
    }
    return item;
  }

  async findByIdWithBankOrNull(id: string): Promise<BankAccountEntity | null> {
    return this.repo.findByIdWithBank(id);
  }

  private async assertBankUsable(bankId: string) {
    const bank = await this.bankRepo.findById(bankId);
    if (!bank) throw new NotFoundException('Bank not found');
    if (!bank.is_active) {
      throw new BadRequestException('Cannot link a bank account to an inactive bank');
    }
  }
}
