import { Repository, FindOptionsWhere, ILike } from 'typeorm';
import { Partner, PartnerAddress, PartnerContact, PartnerBankAccount } from '../entities';
import {
  CreatePartnerDto,
  UpdatePartnerDto,
  CreatePartnerAddressDto,
  CreatePartnerContactDto,
  CreatePartnerBankAccountDto,
} from '../dto';

export interface PartnerSearchParams {
  tenantId: string;
  search?: string;
  partnerType?: 'customer' | 'supplier' | 'both';
  category?: string;
  isActive?: boolean;
  salesRepId?: string;
  limit?: number;
  offset?: number;
}

export class PartnersService {
  constructor(
    private readonly partnerRepository: Repository<Partner>,
    private readonly addressRepository: Repository<PartnerAddress>,
    private readonly contactRepository: Repository<PartnerContact>,
    private readonly bankAccountRepository: Repository<PartnerBankAccount>
  ) {}

  // ==================== Partners ====================

  async findAll(params: PartnerSearchParams): Promise<{ data: Partner[]; total: number }> {
    const {
      tenantId,
      search,
      partnerType,
      category,
      isActive,
      salesRepId,
      limit = 50,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<Partner>[] = [];
    const baseWhere: FindOptionsWhere<Partner> = { tenantId };

    if (partnerType) {
      baseWhere.partnerType = partnerType;
    }

    if (category) {
      baseWhere.category = category;
    }

    if (isActive !== undefined) {
      baseWhere.isActive = isActive;
    }

    if (salesRepId) {
      baseWhere.salesRepId = salesRepId;
    }

    if (search) {
      where.push(
        { ...baseWhere, displayName: ILike(`%${search}%`) },
        { ...baseWhere, legalName: ILike(`%${search}%`) },
        { ...baseWhere, code: ILike(`%${search}%`) },
        { ...baseWhere, taxId: ILike(`%${search}%`) },
        { ...baseWhere, email: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const [data, total] = await this.partnerRepository.findAndCount({
      where,
      take: limit,
      skip: offset,
      order: { displayName: 'ASC' },
    });

    return { data, total };
  }

  async findOne(id: string, tenantId: string): Promise<Partner | null> {
    return this.partnerRepository.findOne({ where: { id, tenantId } });
  }

  async findByCode(code: string, tenantId: string): Promise<Partner | null> {
    return this.partnerRepository.findOne({ where: { code, tenantId } });
  }

  async findByTaxId(taxId: string, tenantId: string): Promise<Partner | null> {
    return this.partnerRepository.findOne({ where: { taxId, tenantId } });
  }

  async create(tenantId: string, dto: CreatePartnerDto, createdBy?: string): Promise<Partner> {
    // Check for existing code
    const existingCode = await this.findByCode(dto.code, tenantId);
    if (existingCode) {
      throw new Error('A partner with this code already exists');
    }

    // Check for existing tax ID
    if (dto.taxId) {
      const existingTaxId = await this.findByTaxId(dto.taxId, tenantId);
      if (existingTaxId) {
        throw new Error('A partner with this tax ID already exists');
      }
    }

    const partner = this.partnerRepository.create({
      ...dto,
      tenantId,
      createdBy,
    });

    return this.partnerRepository.save(partner);
  }

  async update(
    id: string,
    tenantId: string,
    dto: UpdatePartnerDto,
    updatedBy?: string
  ): Promise<Partner | null> {
    const partner = await this.findOne(id, tenantId);
    if (!partner) return null;

    // If changing code, check for duplicates
    if (dto.code && dto.code !== partner.code) {
      const existing = await this.findByCode(dto.code, tenantId);
      if (existing) {
        throw new Error('A partner with this code already exists');
      }
    }

    // If changing tax ID, check for duplicates
    if (dto.taxId && dto.taxId !== partner.taxId) {
      const existing = await this.findByTaxId(dto.taxId, tenantId);
      if (existing && existing.id !== id) {
        throw new Error('A partner with this tax ID already exists');
      }
    }

    Object.assign(partner, {
      ...dto,
      updatedBy,
    });

    return this.partnerRepository.save(partner);
  }

  async delete(id: string, tenantId: string): Promise<boolean> {
    const partner = await this.findOne(id, tenantId);
    if (!partner) return false;

    const result = await this.partnerRepository.softDelete(id);
    return (result.affected ?? 0) > 0;
  }

  async getCustomers(tenantId: string): Promise<Partner[]> {
    return this.partnerRepository.find({
      where: [
        { tenantId, partnerType: 'customer', isActive: true },
        { tenantId, partnerType: 'both', isActive: true },
      ],
      order: { displayName: 'ASC' },
    });
  }

  async getSuppliers(tenantId: string): Promise<Partner[]> {
    return this.partnerRepository.find({
      where: [
        { tenantId, partnerType: 'supplier', isActive: true },
        { tenantId, partnerType: 'both', isActive: true },
      ],
      order: { displayName: 'ASC' },
    });
  }

  // ==================== Addresses ====================

  async getAddresses(partnerId: string): Promise<PartnerAddress[]> {
    return this.addressRepository.find({
      where: { partnerId },
      order: { isDefault: 'DESC', addressType: 'ASC' },
    });
  }

  async createAddress(dto: CreatePartnerAddressDto): Promise<PartnerAddress> {
    // If setting as default, unset other defaults of same type
    if (dto.isDefault) {
      await this.addressRepository.update(
        { partnerId: dto.partnerId, addressType: dto.addressType },
        { isDefault: false }
      );
    }

    const address = this.addressRepository.create(dto);
    return this.addressRepository.save(address);
  }

  async deleteAddress(id: string): Promise<boolean> {
    const result = await this.addressRepository.delete(id);
    return (result.affected ?? 0) > 0;
  }

  // ==================== Contacts ====================

  async getContacts(partnerId: string): Promise<PartnerContact[]> {
    return this.contactRepository.find({
      where: { partnerId },
      order: { isPrimary: 'DESC', fullName: 'ASC' },
    });
  }

  async createContact(dto: CreatePartnerContactDto): Promise<PartnerContact> {
    // If setting as primary, unset other primaries
    if (dto.isPrimary) {
      await this.contactRepository.update({ partnerId: dto.partnerId }, { isPrimary: false });
    }

    const contact = this.contactRepository.create(dto);
    return this.contactRepository.save(contact);
  }

  async deleteContact(id: string): Promise<boolean> {
    const result = await this.contactRepository.delete(id);
    return (result.affected ?? 0) > 0;
  }

  // ==================== Bank Accounts ====================

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

  async createBankAccount(dto: CreatePartnerBankAccountDto): Promise<PartnerBankAccount> {
    // If setting as default, unset other defaults
    if (dto.isDefault) {
      await this.bankAccountRepository.update({ partnerId: dto.partnerId }, { isDefault: false });
    }

    const bankAccount = this.bankAccountRepository.create(dto);
    return this.bankAccountRepository.save(bankAccount);
  }

  async deleteBankAccount(id: string): Promise<boolean> {
    const result = await this.bankAccountRepository.delete(id);
    return (result.affected ?? 0) > 0;
  }

  async verifyBankAccount(id: string): Promise<PartnerBankAccount | null> {
    const bankAccount = await this.bankAccountRepository.findOne({ where: { id } });
    if (!bankAccount) return null;

    bankAccount.isVerified = true;
    bankAccount.verifiedAt = new Date();

    return this.bankAccountRepository.save(bankAccount);
  }
}
