import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { TenantConnectionService } from '../../database/tenant-connection.service';
import { requireCurrentTenant } from '../../database/tenant-context';
import { LoanAccount } from '../../database/tenant/entities/loan-account.entity';
import { LoanPayment } from '../../database/tenant/entities/loan-payment.entity';

export interface CreateLoanDto {
  applicationId?: string;
  memberId?: string;
  officeId?: string;
  loanType?: string;
  loanPurpose?: string;
  loanTitle?: string;
  sanctionedAmount?: number;
  disbursedAmount?: number;
  interestRate?: number;
  termMonths?: number;
  repaymentType?: string;
  disbursementDate?: string;
  disbursementDateBs?: string;
  maturityDate?: string;
  maturityDateBs?: string;
  collateralType?: string;
  collateralDescription?: string;
  collateralValue?: number;
  collateralOwnerName?: string;
  collateralOwnerRelation?: string;
  insuranceCompany?: string;
  insurancePolicyNo?: string;
  insuranceExpiryBs?: string;
  fiscalYear?: string;
  remarks?: string;
}

export interface UpdateLoanDto extends Partial<CreateLoanDto> {
  status?: string;
  nplDate?: string;
  closeDate?: string;
  closeDateBs?: string;
}

export interface MakePaymentDto {
  principalAmount?: number;
  interestAmount?: number;
  penaltyAmount?: number;
  paymentDate?: string;
  paymentDateBs?: string;
  paymentType?: string;
  voucherNumber?: string;
  description?: string;
}

@Injectable()
export class LoansService {
  constructor(private readonly tenantConn: TenantConnectionService) {}

  private sub() {
    return requireCurrentTenant().subdomain;
  }
  private async ds() {
    return this.tenantConn.getTenantDataSource(this.sub());
  }
  private async repo() {
    return (await this.ds()).getRepository(LoanAccount);
  }
  private async payRepo() {
    return (await this.ds()).getRepository(LoanPayment);
  }

  private async nextLoanNumber(): Promise<string> {
    const r = await this.repo();
    const count = await r.count({ withDeleted: true });
    return `LON-${String(count + 1).padStart(5, '0')}`;
  }

  /** Calculate flat-rate EMI */
  static calcEmi(principal: number, ratePercent: number, termMonths: number): number {
    if (termMonths <= 0 || ratePercent <= 0) return principal / (termMonths || 1);
    const monthlyRate = ratePercent / 100 / 12;
    const emi = (principal * monthlyRate * Math.pow(1 + monthlyRate, termMonths))
      / (Math.pow(1 + monthlyRate, termMonths) - 1);
    return Math.round(emi * 100) / 100;
  }

  async findAll(opts: { q?: string; status?: string; loanType?: string; page?: number; limit?: number }) {
    const r = await this.repo();
    const { q, status, loanType, page = 1, limit = 50 } = opts;

    const qb = r
      .createQueryBuilder('l')
      .select([
        'l.id', 'l.loanNumber', 'l.memberId', 'l.officeId', 'l.loanType',
        'l.disbursedAmount', 'l.outstandingBalance', 'l.interestRate',
        'l.termMonths', 'l.disbursementDateBs', 'l.maturityDateBs',
        'l.status', 'l.overdueDays', 'l.createdAt',
      ])
      .leftJoin('l.member', 'member')
      .addSelect(['member.id', 'member.name', 'member.nameEn', 'member.memberId', 'member.photoUrl'])
      .leftJoin('l.office', 'office')
      .addSelect(['office.id', 'office.name'])
      .where('l.deletedAt IS NULL')
      .orderBy('l.createdAt', 'DESC')
      .skip((page - 1) * limit)
      .take(limit);

    if (status)   qb.andWhere('l.status = :status',     { status });
    if (loanType) qb.andWhere('l.loanType = :loanType', { loanType });
    if (q) {
      qb.andWhere(
        '(l.loanNumber LIKE :q OR member.name LIKE :q OR member.nameEn LIKE :q OR member.memberId LIKE :q)',
        { q: `%${q}%` },
      );
    }

    const [items, total] = await qb.getManyAndCount();

    const summary = await r
      .createQueryBuilder('l')
      .select('SUM(l.outstandingBalance)', 'totalOutstanding')
      .addSelect('SUM(l.disbursedAmount)',  'totalDisbursed')
      .addSelect('COUNT(*)',                'totalLoans')
      .addSelect(
        `SUM(CASE WHEN l.status = 'active' THEN 1 ELSE 0 END)`,
        'activeLoans',
      )
      .addSelect(
        `SUM(CASE WHEN l.status = 'NPL' THEN 1 ELSE 0 END)`,
        'nplLoans',
      )
      .where('l.deletedAt IS NULL')
      .getRawOne();

    return {
      items,
      total,
      page,
      limit,
      summary: {
        totalOutstanding: Number(summary?.totalOutstanding ?? 0),
        totalDisbursed:   Number(summary?.totalDisbursed ?? 0),
        totalLoans:       Number(summary?.totalLoans ?? 0),
        activeLoans:      Number(summary?.activeLoans ?? 0),
        nplLoans:         Number(summary?.nplLoans ?? 0),
      },
    };
  }

  async findOne(id: string) {
    const r = await this.repo();
    const loan = await r.findOne({
      where: { id },
      relations: ['member', 'office', 'application'],
    });
    if (!loan) throw new NotFoundException('Loan account not found');
    return loan;
  }

  async findPayments(loanId: string, opts: { page?: number; limit?: number }) {
    await this.findOne(loanId);
    const pr = await this.payRepo();
    const { page = 1, limit = 50 } = opts;
    const [items, total] = await pr.findAndCount({
      where: { loanAccountId: loanId },
      order: { createdAt: 'DESC' },
      skip: (page - 1) * limit,
      take: limit,
    });
    return { items, total, page, limit };
  }

  async create(dto: CreateLoanDto, userId?: string) {
    const r = await this.repo();
    const loanNumber = await this.nextLoanNumber();

    const disbursed = Number(dto.disbursedAmount ?? dto.sanctionedAmount ?? 0);
    const rate = Number(dto.interestRate ?? 0);
    const term = Number(dto.termMonths ?? 0);
    const emi = disbursed > 0 && rate > 0 && term > 0
      ? LoansService.calcEmi(disbursed, rate, term)
      : undefined;

    const loan = r.create({
      ...dto,
      loanNumber,
      outstandingBalance: disbursed,
      totalInterestPaid: 0,
      totalPrincipalPaid: 0,
      penaltyBalance: 0,
      overdueDays: 0,
      status: 'active',
      emiAmount: emi,
      createdBy: userId,
    });
    return r.save(loan);
  }

  async update(id: string, dto: UpdateLoanDto, userId?: string) {
    const r = await this.repo();
    const loan = await this.findOne(id);
    Object.assign(loan, { ...dto, updatedBy: userId });
    return r.save(loan);
  }

  async makePayment(loanId: string, dto: MakePaymentDto, userId?: string) {
    const ds = await this.ds();
    const loanRepo = ds.getRepository(LoanAccount);
    const payRepo  = ds.getRepository(LoanPayment);

    const loan = await loanRepo.findOne({ where: { id: loanId } });
    if (!loan) throw new NotFoundException('Loan account not found');
    if (loan.status === 'closed' || loan.status === 'written_off')
      throw new BadRequestException('यो ऋण खाता बन्द छ। (Loan is closed)');

    const principal = Number(dto.principalAmount ?? 0);
    const interest  = Number(dto.interestAmount  ?? 0);
    const penalty   = Number(dto.penaltyAmount   ?? 0);
    const total     = principal + interest + penalty;

    if (total <= 0) throw new BadRequestException('भुक्तानी रकम शून्य हुन सक्दैन।');
    if (principal > Number(loan.outstandingBalance))
      throw new BadRequestException(`मूल रकम (Principal) मौज्दात भन्दा बढी छ।`);

    const newBalance = Number(loan.outstandingBalance) - principal;

    const payment = payRepo.create({
      loanAccountId:  loanId,
      principalAmount: principal,
      interestAmount:  interest,
      penaltyAmount:   penalty,
      totalAmount:     total,
      balanceAfter:    newBalance,
      paymentDate:     dto.paymentDate,
      paymentDateBs:   dto.paymentDateBs,
      paymentType:     dto.paymentType ?? 'regular',
      voucherNumber:   dto.voucherNumber,
      description:     dto.description,
      createdBy:       userId,
    });

    loan.outstandingBalance  = newBalance;
    loan.totalPrincipalPaid  = Number(loan.totalPrincipalPaid) + principal;
    loan.totalInterestPaid   = Number(loan.totalInterestPaid)  + interest;
    loan.penaltyBalance      = Math.max(0, Number(loan.penaltyBalance) - penalty);
    loan.lastPaymentDate     = dto.paymentDate;
    loan.lastPaymentDateBs   = dto.paymentDateBs;

    if (newBalance <= 0) {
      loan.status    = 'closed';
      loan.closeDate = dto.paymentDate;
      loan.closeDateBs = dto.paymentDateBs;
    }

    await payRepo.save(payment);
    await loanRepo.save(loan);
    return payment;
  }

  async remove(id: string) {
    const r = await this.repo();
    await this.findOne(id);
    await r.softDelete(id);
    return { success: true };
  }
}
