import { Injectable, BadRequestException } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { TEMPLATE_VARIABLES } from '../../config/template-variable.config';

@Injectable()
export class WordGeneratorService {

  buildNepaliDate(adDate?: string): {
    year: string; month: string; monthName: string; date: string;
    dayName: string; dayNum: string; fullDate: string;
  } {
    const today = adDate ? new Date(adDate) : new Date();
    return {
      year:      String(today.getFullYear()),
      month:     String(today.getMonth() + 1).padStart(2, '0'),
      monthName: today.toLocaleString('ne-NP', { month: 'long' }),
      date:      String(today.getDate()).padStart(2, '0'),
      dayName:   today.toLocaleString('ne-NP', { weekday: 'long' }),
      dayNum:    String(today.getDay()),
      fullDate:  today.toISOString().split('T')[0],
    };
  }

  toNepaliDigits(val: string | number | null | undefined): string {
    if (val === null || val === undefined) return '';
    const str = String(val);
    const nepaliDigits = ['०','१','२','३','४','५','६','७','८','९'];
    return str.replace(/[0-9]/g, d => nepaliDigits[parseInt(d)]);
  }

  buildDateVariables(dateStr?: string): Record<string, string> {
    const d = this.buildNepaliDate(dateStr);
    return {
      TodayYear:      this.toNepaliDigits(d.year),
      TodayMonth:     this.toNepaliDigits(d.month),
      TodayMonthName: d.monthName,
      TodayDate:      this.toNepaliDigits(d.date),
      TodayFullDate:  this.toNepaliDigits(d.fullDate),
      TodayDayName:   d.dayName,
      TodayDayNum:    this.toNepaliDigits(d.dayNum),
      CurrentTime:    this.toNepaliDigits(new Date().toLocaleTimeString('ne-NP', { hour: '2-digit', minute: '2-digit' })),
    };
  }

  buildOfficeVariables(office: Record<string, any> | null): Record<string, string> {
    if (!office) return {};
    return {
      SahakariNameEn:             office.nameEn ?? '',
      SahakariNameNP:             office.nameNp ?? office.name ?? '',
      RegisterNikaye:             office.registerNikaye ?? '',
      HeadOffice:                 office.headOffice ?? '',
      SahakariDartaNo:            office.dartaNo ?? office.registrationNo ?? '',
      SahakariDartaDate:          office.dartaDate ?? '',
      SahakariDartaDateBS:        this.toNepaliDigits(office.dartaDateBs ?? ''),
      SAddressProvince:           office.province ?? '',
      SAddressDistrict:           office.district ?? '',
      SAddressLocalGovt:          office.localgovt ?? '',
      SAddressWard:               this.toNepaliDigits(office.ward ?? ''),
      SAddressTole:               office.tole ?? '',
      FormerAddress:              office.formerAddress ?? '',
      SahakariBranchName:         office.branchName ?? office.name ?? '',
      SahakariPresidentName:      office.presidentName ?? '',
      SahakariVicePresidentName:  office.vicePresidentName ?? '',
      SahakariSecretaryName:      office.secretaryName ?? '',
      SahakariTreasurerName:      office.treasurerName ?? '',
      SahakariManagerCEO:         office.managerCeo ?? '',
      LoanCoordinatorName:        office.loanCoordinator ?? '',
      LoanMember1Name:            office.loanMember1 ?? '',
      LoanMember2Name:            office.loanMember2 ?? '',
      SahakariPhone:              office.phone ?? '',
      SahakariEmail:              office.email ?? '',
      Slogan:                     office.slogan ?? '',
      RegisterOffice:             office.registerOffice ?? '',
      HeadOfficeAddress:          [office.province, office.district, office.localgovt, office.ward ? (office.ward + ' नं') : '', office.tole].filter(Boolean).join(', '),
      SahakariPan:                this.toNepaliDigits(office.panNo ?? ''),
      ExpirationDate:             office.expirationDate ?? '',
      ExpirationDateBS:           this.toNepaliDigits(office.expirationDateBs ?? ''),
      OfficeLoanPolicyStatement:  office.loanPolicyStatement ?? '',
      OfficeSavingPolicyStatement:office.savingPolicyStatement ?? '',
    };
  }

  buildMemberVariables(member: Record<string, any> | null): Record<string, string> {
    if (!member) return {};
    return {
      SubscriptionId:                   this.toNepaliDigits(member.subscriptionId ?? ''),
      SubscriptionDate:                 member.subscriptionDate ?? '',
      SubscriptionDateBs:               this.toNepaliDigits(member.subscriptionDateBs ?? ''),
      AccountNumber:                    this.toNepaliDigits(member.accountNumber ?? ''),
      AccountBalance:                   this.toNepaliDigits(member.accountBalance ?? ''),
      ShareAmount:                      this.toNepaliDigits(member.shareAmount ?? ''),
      TotalShareKitta:                  this.toNepaliDigits(member.totalShareKitta ?? ''),
      BorrowerName:                     member.name ?? '',
      BorrowerNameEn:                   member.nameEn ?? '',
      Photo:                            member.photoUrl ?? '',
      Signature:                        member.signatureUrl ?? '',
      BorrowerGender:                   this.mapGender(member.gender),
      BorrowerDateofBirth:              member.dob ?? '',
      BorrowerDateofBirthBS:            this.toNepaliDigits(member.dobBs ?? ''),
      BorrowerAge:                      this.toNepaliDigits(member.age ?? ''),
      BorrowerEduQualification:         member.eduQualification ?? '',
      BorrowerCaste:                    member.casteCode ?? '',
      BorrowerMaritalStatus:            this.mapMaritalStatus(member.maritalStatus),
      Nationality:                      member.nationality ?? 'नेपाली',
      Religion:                         member.religion ?? '',
      Email:                            member.email ?? '',
      ContactNumber:                    this.toNepaliDigits(member.contactNumber ?? ''),
      BorrowerFatherName:               member.fatherName ?? '',
      SpouseName:                       member.spouseName ?? '',
      BorrowerGrandFatherName:          member.grandFatherName ?? '',
      Generations2:                     member.secondGeneration ?? '',
      SonOf:                            member.secondGenerationName ?? '',
      Generations3:                     member.thirdGeneration ?? '',
      GrandSonOf:                       member.thirdGenerationName ?? '',
      BorrowerOccupation:               member.occupation ?? '',
      BorrowerOccupationTitle:          member.occupationTitle ?? '',
      BorrowerOccupationLocation:       member.occupationLocation ?? '',
      BorrowerPermanentProvince:        member.pProvince ?? '',
      BorrowerPermanentDistrict:        member.pDistrict ?? '',
      BorrowerPermanentLocalGovt:       member.pLocalgovt ?? '',
      BorrowerPermanentWard:            this.toNepaliDigits(member.pWard ?? ''),
      BorrowerPermanentTole:            member.pTole ?? '',
      BorrowerTemporaryProvince:        member.tProvince ?? '',
      BorrowerTemporaryDistrict:        member.tDistrict ?? '',
      BorrowerTemporaryLocalGovt:       member.tLocalgovt ?? '',
      BorrowerTemporaryWard:            this.toNepaliDigits(member.tWard ?? ''),
      BorrowerTemporaryTole:            member.tTole ?? '',
      CitizenshipNumber:                this.toNepaliDigits(member.citizenshipNumber ?? ''),
      CitizenshipIssueDate:             member.citizenshipIssueDate ?? '',
      CitizenshipIssueDateBS:           this.toNepaliDigits(member.citizenshipIssueDateBs ?? ''),
      CitizenshipIssueDistrict:         member.citizenshipIssueDistrict ?? '',
      CitizenshipFormerAddress:         member.citizenshipFormerAddress ?? '',
      PanNo:                            this.toNepaliDigits(member.panNo ?? ''),
      PanIssueDate:                     member.panIssueDate ?? '',
      PanIssueDateBS:                   this.toNepaliDigits(member.panIssueDateBs ?? ''),
      PanIssusePlace:                   member.panIssuePlace ?? '',
      NationalIDNo:                     this.toNepaliDigits(member.nationalIdNo ?? ''),
      NationalIDIssueDate:              member.nationalIdIssueDate ?? '',
      NationalIDIssueDateBS:            this.toNepaliDigits(member.nationalIdIssueDateBs ?? ''),
      NationalIDIssusePlace:            member.nationalIdIssuePlace ?? '',
      MonthlyIncome:                    this.toNepaliDigits(member.monthlyIncome ?? ''),
      MonthlyExpenses:                  this.toNepaliDigits(member.monthlyExpenses ?? ''),
      MonthlySaving:                    this.toNepaliDigits(member.monthlySaving ?? ''),
      BorrowerBusinessRegisterNumber:   this.toNepaliDigits(member.businessRegisterNumber ?? ''),
      BorrowerBusinessPanVatNumber:     this.toNepaliDigits(member.businessVatNumber ?? ''),
      BorrowerBusinessRegisterDateBs:   this.toNepaliDigits(member.businessRegisterDateBs ?? ''),
      BorrowerBusinessRegisterOffice:   member.businessRegisterOffice ?? '',
      BorrowerBusinessNature:           member.businessNature ?? '',
      BorrowerBusinessRegistrationType: member.businessRegistrationType ?? '',
      BorrowerBusinessEstimatedCost:    this.toNepaliDigits(member.businessEstimateAnnualIncome ?? ''),
    };
  }

  buildApplicationVariables(app: Record<string, any> | null): Record<string, string> {
    if (!app) return {};
    return {
      ApplicationCode:             app.applicationCode ?? app.code ?? '',
      ApplicantDartaNumber:        this.toNepaliDigits(app.dartaNumber ?? ''),
      ApplicationProfileId:        app.id ?? '',
      LoanApplyDate:               app.loanApplyDate ?? '',
      LoanApplyDateBS:             this.toNepaliDigits(app.loanApplyDateBs ?? ''),
      MeetingDate:                 app.meetingDate ?? '',
      MeetingDateBS:               this.toNepaliDigits(app.meetingDateBs ?? ''),
      MeetingNumber:               this.toNepaliDigits(app.meetingNumber ?? ''),
      RecommendationMeetingDate:   app.recommendationMeetingDate ?? '',
      RecommendationMeetingDateBS: this.toNepaliDigits(app.recommendationMeetingDateBs ?? ''),
      RecommendationMeetingNumber: this.toNepaliDigits(app.recommendationMeetingNumber ?? ''),
      CollateralType:              app.collateralType ?? '',
      LoanDemandAmount:            this.toNepaliDigits(app.loanDemandAmount ?? ''),
      EntryUserName:               app.writerByName ?? '',
      EntryUserPosition:           app.writerByPosition ?? '',
      EntryUserCode:               app.writerCode ?? '',
      EntryUserSignature:          app.writerSignature ?? '',
      ReviewUserName:              app.reviewByName ?? '',
      ReviewUserPosition:          app.reviewByPosition ?? '',
      ReviewUserCode:              app.reviewCode ?? '',
      ReviewUserSignature:         app.reviewSignature ?? '',
      ApprovedUserName:            app.approvedByName ?? '',
      ApprovedUserPosition:        app.approvedByPosition ?? '',
      ApprovedUserCode:            app.approvedCode ?? '',
      ApprovedUserSignature:       app.approvedSignature ?? '',
      LoanAccountNumber:           this.toNepaliDigits(app.loanAccountNumber ?? ''),
    };
  }

  buildLoanVariables(loan: Record<string, any> | null): Record<string, string> {
    if (!loan) return {};
    return {
      LoanAccountNumber:      this.toNepaliDigits(loan.loanAccountNumber ?? loan.loanNumber ?? ''),
      TotalLoanAmount:        this.toNepaliDigits(loan.totalLoanAmount ?? loan.sanctionedAmount ?? ''),
      TotalLoanAmountInWord:  loan.totalLoanAmountInWord ?? '',
      LoanAmount:             this.toNepaliDigits(loan.loanAmount ?? loan.disbursedAmount ?? ''),
      LoanAmountInWord:       loan.loanAmountInWord ?? '',
      Loantitle:              loan.loanTitle ?? '',
      LoanTitleCode:          loan.loanTitleCode ?? '',
      LoanPurpose:            loan.loanPurpose ?? '',
      LoanInterestRate:       this.toNepaliDigits(loan.loanInterestRate ?? loan.interestRate ?? ''),
      LoanInterestRateInWord: loan.interestRateInWord ?? '',
      LoanPeriod:             this.toNepaliDigits(loan.loanPeriod ?? loan.termMonths ?? ''),
      LoanPeriodType:         loan.loanPeriodType ?? '',
      GracePeriod:            this.toNepaliDigits(loan.gracePeriod ?? ''),
      GraceType:              loan.graceType ?? '',
      InterestType:           loan.interestType ?? '',
      PaymentAmount:          this.toNepaliDigits(loan.paymentAmount ?? loan.emiAmount ?? ''),
      PaymentType:            loan.paymentType ?? loan.repaymentType ?? '',
      KistaTotal:             this.toNepaliDigits(loan.kistaTotal ?? ''),
      LoanInvestmentDate:     loan.investmentDate ?? loan.disbursementDate ?? '',
      LoanInvestmentDateBS:   this.toNepaliDigits(loan.investmentDateBs ?? loan.disbursementDateBs ?? ''),
      LoanExpiryDate:         loan.expirationDate ?? loan.maturityDate ?? '',
      LoanExpiryDateBS:       this.toNepaliDigits(loan.expirationDateBs ?? loan.maturityDateBs ?? ''),
      LoanRepaymentDate:      loan.loanPassDate ?? '',
      LoanRepaymentDateBs:    this.toNepaliDigits(loan.loanPassDateBs ?? ''),
      PaymentVoucherNumber:   this.toNepaliDigits(loan.paymentVoucherNumber ?? ''),
      TamsukWriter:           loan.tamsukWriter ?? '',
      TamsukApprovedBy:       loan.tamsukApprovedBy ?? '',
      Recommendator:          loan.recommendator ?? '',
      TamsukProvenBy:         loan.tamsukProvenBy ?? '',
    };
  }

  buildGuarantorVariables(guarantor: Record<string, any> | null, index = 0): Record<string, string> {
    if (!guarantor) return {};
    return {
      G_photo:                           guarantor.photoUrl ?? '',
      GuarantorName:                     guarantor.guarantorName ?? guarantor.name ?? '',
      GuarantorFatherName:               guarantor.fatherName ?? '',
      GuarantorGenerations2:             guarantor.secondGeneration ?? '',
      GuarantorSonOf:                    guarantor.secondGenerationName ?? '',
      GuarantorGenerations3:             guarantor.thirdGeneration ?? '',
      GuarantorGrandSonOf:               guarantor.thirdGenerationName ?? '',
      GuarantorGrandFatherName:          guarantor.grandFatherName ?? '',
      GuarantorSpouseName:               guarantor.spouseName ?? '',
      GuarantorRelation:                 guarantor.relation ?? '',
      GuarantorSubscriptionId:           this.toNepaliDigits(guarantor.subscriptionId ?? ''),
      GuarantorDOB:                      guarantor.dob ?? '',
      GuarantorDOBBS:                    this.toNepaliDigits(guarantor.dobBs ?? ''),
      GuarantorAge:                      this.toNepaliDigits(guarantor.age ?? ''),
      GuarantorContactNumber:            this.toNepaliDigits(guarantor.contactNumber ?? ''),
      GuarantorCitizenshipNumber:        this.toNepaliDigits(guarantor.citizenshipNumber ?? ''),
      GuarantorCitizenshipIssueDate:     guarantor.citizenshipIssueDate ?? '',
      GuarantorCitizenshipIssueDateBS:   this.toNepaliDigits(guarantor.citizenshipIssueDateBs ?? ''),
      GuarantorCitizenshipIssueDistrict: guarantor.citizenshipIssueDistrict ?? '',
      GuarantorFormerAddress:            guarantor.formerAddress ?? '',
      GuarantorPermanentProvince:        guarantor.province ?? '',
      GuarantorPermanentDistrict:        guarantor.district ?? '',
      GuarantorPermanentLocalGovt:       guarantor.localgovt ?? '',
      GuarantorPermanentWard:            this.toNepaliDigits(guarantor.ward ?? ''),
      GuarantorPermanentTole:            guarantor.tole ?? '',
      GuarantorTemporaryProvince:        guarantor.tProvince ?? '',
      GuarantorTemporaryDistrict:        guarantor.tDistrict ?? '',
      GuarantorTemporaryLocalGovt:       guarantor.tLocalgovt ?? '',
      GuarantorTemporaryWard:            this.toNepaliDigits(guarantor.tWard ?? ''),
      GuarantorTemporaryTole:            guarantor.tTole ?? '',
      GuarantorSubscriptionNumber:       this.toNepaliDigits(guarantor.subscriptionId ?? ''),
    };
  }

  mapGender(val: string | null | undefined): string {
    if (!val) return '';
    const map: Record<string, string> = { male: 'पुरुष', female: 'महिला', other: 'अन्य', M: 'पुरुष', F: 'महिला' };
    return map[val] ?? val;
  }

  mapMaritalStatus(val: string | null | undefined): string {
    if (!val) return '';
    const map: Record<string, string> = {
      single: 'अविवाहित', married: 'विवाहित', divorced: 'सम्बन्ध विच्छेद',
      widowed: 'बिधुवा/बिधुर', separated: 'अलगथलग',
    };
    return map[val] ?? val;
  }

  async generateDocx(
    templateBuffer: Buffer,
    variables: Record<string, string>,
  ): Promise<Buffer> {
    try {
      const PizZip = (await import('pizzip')).default;
      const Docxtemplater = (await import('docxtemplater')).default;

      const zip = new PizZip(templateBuffer);
      const doc = new Docxtemplater(zip, {
        paragraphLoop: true,
        linebreaks: true,
        nullGetter: () => '',
      });

      doc.render(variables);

      const buf = doc.getZip().generate({
        type: 'nodebuffer',
        compression: 'DEFLATE',
      });
      return buf;
    } catch (err: any) {
      const message = err?.properties?.errors
        ? err.properties.errors.map((e: any) => e.message).join('; ')
        : err.message;
      throw new BadRequestException(`Word document generation failed: ${message}`);
    }
  }

  async generateDocxFromPath(
    templatePath: string,
    variables: Record<string, string>,
  ): Promise<Buffer> {
    if (!fs.existsSync(templatePath)) {
      throw new BadRequestException(`Template file not found: ${path.basename(templatePath)}`);
    }
    const buffer = fs.readFileSync(templatePath);
    return this.generateDocx(buffer, variables);
  }

  mergeVariables(...maps: Record<string, string>[]): Record<string, string> {
    return Object.assign({}, ...maps);
  }

  getVariableCatalog() {
    return TEMPLATE_VARIABLES;
  }

  getAllVariableNames(): string[] {
    const names: string[] = [];
    for (const group of Object.values(TEMPLATE_VARIABLES)) {
      for (const v of Object.values(group.variables)) {
        names.push(v.templateName);
      }
    }
    return names;
  }
}
