import { Injectable, NotFoundException } from '@nestjs/common';
import { TenantConnectionService } from '../../database/tenant-connection.service';
import { Application } from '../../database/tenant/entities/application.entity';
import { ApplicantSaving } from '../../database/tenant/entities/applicant-saving.entity';
import { ApplicantIncome } from '../../database/tenant/entities/applicant-income.entity';
import { ApplicantCharge } from '../../database/tenant/entities/applicant-charge.entity';
import { Guarantor } from '../../database/tenant/entities/guarantor.entity';
import { ApplicantBusinessIncome } from '../../database/tenant/entities/applicant-business-income.entity';
import { ApplicantPayableInstitution } from '../../database/tenant/entities/applicant-payable-institution.entity';
import { ApplicantFinancialTransaction } from '../../database/tenant/entities/applicant-financial-transaction.entity';
import { ApplicationRelation } from '../../database/tenant/entities/application-relation.entity';
import { CreateApplicationDto, UpdateApplicationDto } from './application.dto';

const ALL_RELATIONS = [
  'member', 'office',
  'savings', 'incomes', 'charges',
  'businessIncomes', 'payableInstitutions', 'financialTransactions',
  'relations', 'guarantors',
];

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

  private async ds(sub: string) { return this.tenantConn.getTenantDataSource(sub); }
  private async repo(sub: string) { return (await this.ds(sub)).getRepository(Application); }

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

  async findAll(sub: string, opts: { q?: string; status?: string; page?: number; limit?: number }) {
    const r = await this.repo(sub);
    const { q, status, page = 1, limit = 20 } = opts;
    const qb = r.createQueryBuilder('a')
      .leftJoinAndSelect('a.member', 'member')
      .leftJoinAndSelect('a.office', 'office')
      .where('a.deletedAt IS NULL')
      .orderBy('a.createdAt', 'DESC')
      .skip((page - 1) * limit)
      .take(limit);

    if (status) qb.andWhere('a.status = :status', { status });
    if (q) {
      qb.andWhere(
        '(a.applicationNumber LIKE :q OR member.fullName LIKE :q OR member.memberNo LIKE :q)',
        { q: `%${q}%` },
      );
    }

    const [items, total] = await qb.getManyAndCount();
    return { items, total, page, limit };
  }

  async findOne(sub: string, id: string) {
    const r = await this.repo(sub);
    const a = await r.findOne({ where: { id }, relations: ALL_RELATIONS });
    if (!a) throw new NotFoundException('Application not found');
    return a;
  }

  async create(sub: string, dto: CreateApplicationDto, createdBy?: string) {
    const database = await this.ds(sub);
    const r = database.getRepository(Application);
    const applicationNumber = await this.nextNumber(sub);
    const { savings, incomes, charges, businessIncomes, payableInstitutions, financialTransactions, relations, guarantors, ...rest } = dto;

    const app = r.create({ ...rest, applicationNumber, status: dto.status ?? 'draft', createdBy });

    if (savings?.length)
      app.savings = savings.map((s, i) => database.getRepository(ApplicantSaving).create({ ...s, order: i }));
    if (incomes?.length)
      app.incomes = incomes.map((s, i) => database.getRepository(ApplicantIncome).create({ ...s, order: i }));
    if (charges?.length)
      app.charges = charges.map((s, i) => database.getRepository(ApplicantCharge).create({ ...s, order: i }));
    if (businessIncomes?.length)
      app.businessIncomes = businessIncomes.map((s, i) => database.getRepository(ApplicantBusinessIncome).create({ ...s, order: i }));
    if (payableInstitutions?.length)
      app.payableInstitutions = payableInstitutions.map((s, i) => database.getRepository(ApplicantPayableInstitution).create({ ...s, order: i }));
    if (financialTransactions?.length)
      app.financialTransactions = financialTransactions.map((s, i) => database.getRepository(ApplicantFinancialTransaction).create({ ...s, order: i }));
    if (relations?.length)
      app.relations = relations.map((s, i) => database.getRepository(ApplicationRelation).create({ ...s, order: i }));
    if (guarantors?.length)
      app.guarantors = guarantors.map((s, i) => database.getRepository(Guarantor).create({ ...s, order: i }));

    return r.save(app);
  }

  async update(sub: string, id: string, dto: UpdateApplicationDto, updatedBy?: string) {
    const database = await this.ds(sub);
    const r = database.getRepository(Application);
    const app = await this.findOne(sub, id);
    const { savings, incomes, charges, businessIncomes, payableInstitutions, financialTransactions, relations, guarantors, ...rest } = dto;
    Object.assign(app, { ...rest, updatedBy });

    if (savings !== undefined) {
      await database.getRepository(ApplicantSaving).delete({ applicationId: id });
      app.savings = savings.map((s, i) => database.getRepository(ApplicantSaving).create({ ...s, applicationId: id, order: i }));
    }
    if (incomes !== undefined) {
      await database.getRepository(ApplicantIncome).delete({ applicationId: id });
      app.incomes = incomes.map((s, i) => database.getRepository(ApplicantIncome).create({ ...s, applicationId: id, order: i }));
    }
    if (charges !== undefined) {
      await database.getRepository(ApplicantCharge).delete({ applicationId: id });
      app.charges = charges.map((s, i) => database.getRepository(ApplicantCharge).create({ ...s, applicationId: id, order: i }));
    }
    if (businessIncomes !== undefined) {
      await database.getRepository(ApplicantBusinessIncome).delete({ applicationId: id });
      app.businessIncomes = businessIncomes.map((s, i) => database.getRepository(ApplicantBusinessIncome).create({ ...s, applicationId: id, order: i }));
    }
    if (payableInstitutions !== undefined) {
      await database.getRepository(ApplicantPayableInstitution).delete({ applicationId: id });
      app.payableInstitutions = payableInstitutions.map((s, i) => database.getRepository(ApplicantPayableInstitution).create({ ...s, applicationId: id, order: i }));
    }
    if (financialTransactions !== undefined) {
      await database.getRepository(ApplicantFinancialTransaction).delete({ applicationId: id });
      app.financialTransactions = financialTransactions.map((s, i) => database.getRepository(ApplicantFinancialTransaction).create({ ...s, applicationId: id, order: i }));
    }
    if (relations !== undefined) {
      await database.getRepository(ApplicationRelation).delete({ applicationId: id });
      app.relations = relations.map((s, i) => database.getRepository(ApplicationRelation).create({ ...s, applicationId: id, order: i }));
    }
    if (guarantors !== undefined) {
      await database.getRepository(Guarantor).delete({ applicationId: id });
      app.guarantors = guarantors.map((s, i) => database.getRepository(Guarantor).create({ ...s, applicationId: id, order: i }));
    }

    return r.save(app);
  }

  async updateStatus(sub: string, id: string, status: string, updatedBy?: string) {
    const r = await this.repo(sub);
    await r.update(id, { status, updatedBy });
    return this.findOne(sub, id);
  }

  async remove(sub: string, id: string) {
    const r = await this.repo(sub);
    const a = await this.findOne(sub, id);
    await r.softRemove(a);
    return { ok: true };
  }

  // ── Sub-entity CRUD helpers ───────────────────────────────

  async getSubItems<T>(sub: string, Entity: any, applicationId: string): Promise<T[]> {
    const db = await this.ds(sub);
    return db.getRepository(Entity).find({ where: { applicationId }, order: { order: 'ASC' } }) as Promise<T[]>;
  }

  async addSubItem<T>(sub: string, Entity: any, applicationId: string, data: Partial<T>, createdBy?: string): Promise<T> {
    const db = await this.ds(sub);
    const repo = db.getRepository(Entity);
    const count = await repo.count({ where: { applicationId } });
    const item = repo.create({ ...data, applicationId, order: count, createdBy });
    return repo.save(item) as Promise<T>;
  }

  async updateSubItem<T>(sub: string, Entity: any, applicationId: string, itemId: string, data: Partial<T>, updatedBy?: string): Promise<T> {
    const db = await this.ds(sub);
    const repo = db.getRepository(Entity);
    const item = await repo.findOne({ where: { id: itemId, applicationId } });
    if (!item) throw new NotFoundException('Item not found');
    Object.assign(item, { ...data, updatedBy });
    return repo.save(item) as Promise<T>;
  }

  async removeSubItem(sub: string, Entity: any, applicationId: string, itemId: string): Promise<{ ok: boolean }> {
    const db = await this.ds(sub);
    const repo = db.getRepository(Entity);
    const item = await repo.findOne({ where: { id: itemId, applicationId } });
    if (!item) throw new NotFoundException('Item not found');
    await repo.softRemove(item);
    return { ok: true };
  }
}
