/**
 * Partner Bank Account Service
 * Servicio para gestion de cuentas bancarias de socios comerciales
 *
 * @module Partners
 */

import { DataSource, Repository } from 'typeorm';
import { PartnerBankAccount } from '../entities';

/**
 * Service context for multi-tenant operations
 */
export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export type AccountType = 'checking' | 'savings';

export interface CreatePartnerBankAccountDto {
  partnerId: string;
  bankName: string;
  bankCode?: string;
  accountNumber: string;
  clabe?: string;
  accountType?: AccountType;
  currency?: string;
  beneficiaryName?: string;
  beneficiaryTaxId?: string;
  swiftCode?: string;
  isDefault?: boolean;
  notes?: string;
}

export interface UpdatePartnerBankAccountDto {
  bankName?: string;
  bankCode?: string;
  accountNumber?: string;
  clabe?: string;
  accountType?: AccountType;
  currency?: string;
  beneficiaryName?: string;
  beneficiaryTaxId?: string;
  swiftCode?: string;
  isDefault?: boolean;
  notes?: string;
}

export class PartnerBankAccountService {
  private readonly repository: Repository<PartnerBankAccount>;

  constructor(dataSource: DataSource) {
    this.repository = dataSource.getRepository(PartnerBankAccount);
  }

  async findByPartnerId(partnerId: string): Promise<PartnerBankAccount[]> {
    return this.repository.find({
      where: { partnerId },
      order: { isDefault: 'DESC', createdAt: 'DESC' },
    });
  }

  async findById(id: string): Promise<PartnerBankAccount | null> {
    return this.repository.findOne({
      where: { id },
      relations: ['partner'],
    });
  }

  async findDefaultAccount(partnerId: string): Promise<PartnerBankAccount | null> {
    return this.repository.findOne({
      where: { partnerId, isDefault: true },
    });
  }

  async findVerifiedAccounts(partnerId: string): Promise<PartnerBankAccount[]> {
    return this.repository.find({
      where: { partnerId, isVerified: true },
      order: { isDefault: 'DESC', bankName: 'ASC' },
    });
  }

  async findByClabe(clabe: string): Promise<PartnerBankAccount | null> {
    return this.repository.findOne({
      where: { clabe },
      relations: ['partner'],
    });
  }

  async create(
    _ctx: ServiceContext,
    dto: CreatePartnerBankAccountDto
  ): Promise<PartnerBankAccount> {
    // Validate CLABE format for Mexican banks
    if (dto.clabe && dto.clabe.length !== 18) {
      throw new Error('CLABE must be exactly 18 digits');
    }

    // Check if CLABE already exists
    if (dto.clabe) {
      const existingClabe = await this.findByClabe(dto.clabe);
      if (existingClabe) {
        throw new Error('A bank account with this CLABE already exists');
      }
    }

    // If this is default, clear other defaults
    if (dto.isDefault) {
      await this.clearDefaultAccount(dto.partnerId);
    }

    // Check if this is the first account
    const existingAccounts = await this.findByPartnerId(dto.partnerId);
    const isFirst = existingAccounts.length === 0;

    const account = this.repository.create({
      ...dto,
      isDefault: dto.isDefault ?? isFirst,
      accountType: dto.accountType ?? 'checking',
      currency: dto.currency ?? 'MXN',
    });

    return this.repository.save(account);
  }

  async update(
    _ctx: ServiceContext,
    id: string,
    dto: UpdatePartnerBankAccountDto
  ): Promise<PartnerBankAccount | null> {
    const account = await this.findById(id);
    if (!account) {
      return null;
    }

    // Validate CLABE format if being updated
    if (dto.clabe && dto.clabe.length !== 18) {
      throw new Error('CLABE must be exactly 18 digits');
    }

    // Check for duplicate CLABE if being updated
    if (dto.clabe && dto.clabe !== account.clabe) {
      const existingClabe = await this.findByClabe(dto.clabe);
      if (existingClabe) {
        throw new Error('A bank account with this CLABE already exists');
      }
    }

    // If setting as default, clear other defaults
    if (dto.isDefault && !account.isDefault) {
      await this.clearDefaultAccount(account.partnerId);
    }

    Object.assign(account, dto);
    return this.repository.save(account);
  }

  async setAsDefault(id: string): Promise<PartnerBankAccount | null> {
    const account = await this.findById(id);
    if (!account) {
      return null;
    }

    // Clear other defaults
    await this.clearDefaultAccount(account.partnerId);

    account.isDefault = true;
    return this.repository.save(account);
  }

  async verify(_ctx: ServiceContext, id: string): Promise<PartnerBankAccount | null> {
    const account = await this.findById(id);
    if (!account) {
      return null;
    }

    account.isVerified = true;
    account.verifiedAt = new Date();
    return this.repository.save(account);
  }

  async unverify(_ctx: ServiceContext, id: string): Promise<PartnerBankAccount | null> {
    const account = await this.findById(id);
    if (!account) {
      return null;
    }

    account.isVerified = false;
    account.verifiedAt = null;
    return this.repository.save(account);
  }

  async delete(id: string): Promise<boolean> {
    const account = await this.findById(id);
    if (!account) {
      return false;
    }

    const wasDefault = account.isDefault;
    const partnerId = account.partnerId;

    const result = await this.repository.delete({ id });

    // If deleted account was default, set another as default
    if (wasDefault && result.affected && result.affected > 0) {
      const remaining = await this.repository.findOne({
        where: { partnerId },
        order: { createdAt: 'ASC' },
      });

      if (remaining) {
        remaining.isDefault = true;
        await this.repository.save(remaining);
      }
    }

    return result.affected ? result.affected > 0 : false;
  }

  private async clearDefaultAccount(partnerId: string): Promise<void> {
    await this.repository.update(
      { partnerId, isDefault: true },
      { isDefault: false }
    );
  }

  async getMaskedAccountNumber(id: string): Promise<string | null> {
    const account = await this.findById(id);
    if (!account) {
      return null;
    }

    const accountNumber = account.accountNumber;
    if (accountNumber.length <= 4) {
      return '*'.repeat(accountNumber.length);
    }

    return '*'.repeat(accountNumber.length - 4) + accountNumber.slice(-4);
  }

  async getAccountSummary(partnerId: string): Promise<{
    total: number;
    verified: number;
    hasDefault: boolean;
    currencies: string[];
  }> {
    const accounts = await this.findByPartnerId(partnerId);

    const currencies = [...new Set(accounts.map(a => a.currency))];

    return {
      total: accounts.length,
      verified: accounts.filter(a => a.isVerified).length,
      hasDefault: accounts.some(a => a.isDefault),
      currencies,
    };
  }
}
