/**
 * Partner Address Service
 * Servicio para gestion de direcciones de socios comerciales
 *
 * @module Partners
 */

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

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

export type AddressType = 'billing' | 'shipping' | 'both';

export interface CreatePartnerAddressDto {
  partnerId: string;
  addressType: AddressType;
  isDefault?: boolean;
  label?: string;
  street: string;
  exteriorNumber?: string;
  interiorNumber?: string;
  neighborhood?: string;
  city: string;
  municipality?: string;
  state: string;
  postalCode: string;
  country?: string;
  reference?: string;
  latitude?: number;
  longitude?: number;
}

export interface UpdatePartnerAddressDto {
  addressType?: AddressType;
  isDefault?: boolean;
  label?: string;
  street?: string;
  exteriorNumber?: string;
  interiorNumber?: string;
  neighborhood?: string;
  city?: string;
  municipality?: string;
  state?: string;
  postalCode?: string;
  country?: string;
  reference?: string;
  latitude?: number;
  longitude?: number;
}

export class PartnerAddressService {
  private readonly repository: Repository<PartnerAddress>;

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

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

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

  async findDefaultByType(
    partnerId: string,
    addressType: AddressType
  ): Promise<PartnerAddress | null> {
    // For 'both' type, any address can be default
    const queryBuilder = this.repository
      .createQueryBuilder('address')
      .where('address.partner_id = :partnerId', { partnerId })
      .andWhere('address.is_default = true');

    if (addressType !== 'both') {
      queryBuilder.andWhere(
        '(address.address_type = :addressType OR address.address_type = :both)',
        { addressType, both: 'both' }
      );
    }

    return queryBuilder.getOne();
  }

  async findBillingAddresses(partnerId: string): Promise<PartnerAddress[]> {
    return this.repository.find({
      where: [
        { partnerId, addressType: 'billing' },
        { partnerId, addressType: 'both' },
      ],
      order: { isDefault: 'DESC', createdAt: 'DESC' },
    });
  }

  async findShippingAddresses(partnerId: string): Promise<PartnerAddress[]> {
    return this.repository.find({
      where: [
        { partnerId, addressType: 'shipping' },
        { partnerId, addressType: 'both' },
      ],
      order: { isDefault: 'DESC', createdAt: 'DESC' },
    });
  }

  async create(
    _ctx: ServiceContext,
    dto: CreatePartnerAddressDto
  ): Promise<PartnerAddress> {
    // If this is the first address or marked as default, update others
    if (dto.isDefault) {
      await this.clearDefaultForType(dto.partnerId, dto.addressType);
    }

    // Check if this is the first address of this type
    const existingAddresses = await this.findByPartnerId(dto.partnerId);
    const isFirstOfType = !existingAddresses.some(
      addr => addr.addressType === dto.addressType || addr.addressType === 'both'
    );

    const address = this.repository.create({
      ...dto,
      isDefault: dto.isDefault ?? isFirstOfType,
      country: dto.country ?? 'MEX',
    });

    return this.repository.save(address);
  }

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

    // If setting as default, clear other defaults
    if (dto.isDefault && !address.isDefault) {
      await this.clearDefaultForType(address.partnerId, dto.addressType ?? address.addressType);
    }

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

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

    // Clear other defaults for this type
    await this.clearDefaultForType(address.partnerId, address.addressType);

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

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

    const wasDefault = address.isDefault;
    const partnerId = address.partnerId;
    const addressType = address.addressType;

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

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

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

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

  private async clearDefaultForType(
    partnerId: string,
    addressType: AddressType
  ): Promise<void> {
    await this.repository
      .createQueryBuilder()
      .update(PartnerAddress)
      .set({ isDefault: false })
      .where('partner_id = :partnerId', { partnerId })
      .andWhere('is_default = true')
      .andWhere(
        '(address_type = :addressType OR address_type = :both OR :addressType = :both)',
        { addressType, both: 'both' }
      )
      .execute();
  }

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

    const parts = [
      address.street,
      address.exteriorNumber ? `#${address.exteriorNumber}` : null,
      address.interiorNumber ? `Int. ${address.interiorNumber}` : null,
      address.neighborhood,
      address.city,
      address.municipality,
      address.state,
      `C.P. ${address.postalCode}`,
      address.country,
    ].filter(Boolean);

    return parts.join(', ');
  }
}
