/**
 * Partner Service
 * Servicio para gestion de socios comerciales (clientes, proveedores)
 *
 * @module Partners
 */

import { DataSource, Repository, FindOptionsWhere } from 'typeorm';
import { Partner, PartnerType } from '../entities';

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

/**
 * Paginated result
 */
export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreatePartnerDto {
  code: string;
  displayName: string;
  legalName?: string;
  partnerType: PartnerType;
  taxId?: string;
  taxRegime?: string;
  cfdiUse?: string;
  email?: string;
  phone?: string;
  mobile?: string;
  website?: string;
  paymentTermDays?: number;
  creditLimit?: number;
  priceListId?: string;
  discountPercent?: number;
  category?: string;
  tags?: string[];
  notes?: string;
  salesRepId?: string;
}

export interface UpdatePartnerDto {
  displayName?: string;
  legalName?: string;
  partnerType?: PartnerType;
  taxId?: string;
  taxRegime?: string;
  cfdiUse?: string;
  email?: string;
  phone?: string;
  mobile?: string;
  website?: string;
  paymentTermDays?: number;
  creditLimit?: number;
  priceListId?: string;
  discountPercent?: number;
  category?: string;
  tags?: string[];
  notes?: string;
  salesRepId?: string;
  isActive?: boolean;
  isVerified?: boolean;
}

export interface PartnerFilters {
  partnerType?: PartnerType;
  category?: string;
  isActive?: boolean;
  isVerified?: boolean;
  salesRepId?: string;
  search?: string;
  minCreditLimit?: number;
  maxCreditLimit?: number;
}

export class PartnerService {
  private readonly repository: Repository<Partner>;

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

  async findWithFilters(
    ctx: ServiceContext,
    filters: PartnerFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<Partner>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.repository
      .createQueryBuilder('partner')
      .where('partner.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('partner.deleted_at IS NULL');

    if (filters.partnerType) {
      queryBuilder.andWhere('partner.partner_type = :partnerType', {
        partnerType: filters.partnerType,
      });
    }

    if (filters.category) {
      queryBuilder.andWhere('partner.category = :category', {
        category: filters.category,
      });
    }

    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('partner.is_active = :isActive', {
        isActive: filters.isActive,
      });
    }

    if (filters.isVerified !== undefined) {
      queryBuilder.andWhere('partner.is_verified = :isVerified', {
        isVerified: filters.isVerified,
      });
    }

    if (filters.salesRepId) {
      queryBuilder.andWhere('partner.sales_rep_id = :salesRepId', {
        salesRepId: filters.salesRepId,
      });
    }

    if (filters.search) {
      queryBuilder.andWhere(
        '(partner.display_name ILIKE :search OR partner.code ILIKE :search OR partner.tax_id ILIKE :search OR partner.email ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    if (filters.minCreditLimit !== undefined) {
      queryBuilder.andWhere('partner.credit_limit >= :minCreditLimit', {
        minCreditLimit: filters.minCreditLimit,
      });
    }

    if (filters.maxCreditLimit !== undefined) {
      queryBuilder.andWhere('partner.credit_limit <= :maxCreditLimit', {
        maxCreditLimit: filters.maxCreditLimit,
      });
    }

    queryBuilder
      .orderBy('partner.display_name', 'ASC')
      .skip(skip)
      .take(limit);

    const [data, total] = await queryBuilder.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  async findById(ctx: ServiceContext, id: string): Promise<Partner | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<Partner>,
    });
  }

  async findByCode(ctx: ServiceContext, code: string): Promise<Partner | null> {
    return this.repository.findOne({
      where: {
        code,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<Partner>,
    });
  }

  async findByTaxId(ctx: ServiceContext, taxId: string): Promise<Partner | null> {
    return this.repository.findOne({
      where: {
        taxId,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<Partner>,
    });
  }

  async findCustomers(
    ctx: ServiceContext,
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<Partner>> {
    return this.findWithFilters(ctx, { partnerType: 'customer', isActive: true }, page, limit);
  }

  async findSuppliers(
    ctx: ServiceContext,
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<Partner>> {
    return this.findWithFilters(ctx, { partnerType: 'supplier', isActive: true }, page, limit);
  }

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

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

    const partner = this.repository.create({
      tenantId: ctx.tenantId,
      createdBy: ctx.userId,
      ...dto,
    });

    return this.repository.save(partner);
  }

  async update(
    ctx: ServiceContext,
    id: string,
    dto: UpdatePartnerDto
  ): Promise<Partner | null> {
    const partner = await this.findById(ctx, id);
    if (!partner) {
      return null;
    }

    // Check for duplicate taxId if being updated
    if (dto.taxId && dto.taxId !== partner.taxId) {
      const existingTaxId = await this.findByTaxId(ctx, dto.taxId);
      if (existingTaxId) {
        throw new Error('A partner with this tax ID already exists');
      }
    }

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

    return this.repository.save(partner);
  }

  async updateBalance(
    ctx: ServiceContext,
    id: string,
    balanceChange: number
  ): Promise<Partner | null> {
    const partner = await this.findById(ctx, id);
    if (!partner) {
      return null;
    }

    partner.currentBalance = Number(partner.currentBalance) + balanceChange;
    partner.updatedBy = ctx.userId ?? '';

    return this.repository.save(partner);
  }

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

    partner.isVerified = true;
    partner.updatedBy = ctx.userId ?? '';

    return this.repository.save(partner);
  }

  async activate(ctx: ServiceContext, id: string): Promise<Partner | null> {
    const partner = await this.findById(ctx, id);
    if (!partner) {
      return null;
    }

    partner.isActive = true;
    partner.updatedBy = ctx.userId ?? '';

    return this.repository.save(partner);
  }

  async deactivate(ctx: ServiceContext, id: string): Promise<Partner | null> {
    const partner = await this.findById(ctx, id);
    if (!partner) {
      return null;
    }

    partner.isActive = false;
    partner.updatedBy = ctx.userId ?? '';

    return this.repository.save(partner);
  }

  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const partner = await this.findById(ctx, id);
    if (!partner) {
      return false;
    }

    // Check if partner has outstanding balance
    if (Number(partner.currentBalance) !== 0) {
      throw new Error('Cannot delete partner with outstanding balance');
    }

    await this.repository.update(
      { id, tenantId: ctx.tenantId } as FindOptionsWhere<Partner>,
      { deletedAt: new Date() }
    );

    return true;
  }

  async getStatistics(ctx: ServiceContext): Promise<{
    totalPartners: number;
    customers: number;
    suppliers: number;
    both: number;
    active: number;
    inactive: number;
    verified: number;
    totalCreditLimit: number;
    totalBalance: number;
  }> {
    const partners = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<Partner>,
    });

    return {
      totalPartners: partners.length,
      customers: partners.filter(p => p.partnerType === 'customer').length,
      suppliers: partners.filter(p => p.partnerType === 'supplier').length,
      both: partners.filter(p => p.partnerType === 'both').length,
      active: partners.filter(p => p.isActive).length,
      inactive: partners.filter(p => !p.isActive).length,
      verified: partners.filter(p => p.isVerified).length,
      totalCreditLimit: partners.reduce((sum, p) => sum + Number(p.creditLimit || 0), 0),
      totalBalance: partners.reduce((sum, p) => sum + Number(p.currentBalance || 0), 0),
    };
  }

  async getCategories(ctx: ServiceContext): Promise<string[]> {
    const result = await this.repository
      .createQueryBuilder('partner')
      .select('DISTINCT partner.category', 'category')
      .where('partner.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('partner.deleted_at IS NULL')
      .andWhere('partner.category IS NOT NULL')
      .orderBy('partner.category', 'ASC')
      .getRawMany();

    return result.map(r => r.category);
  }

  async search(
    ctx: ServiceContext,
    query: string,
    limit: number = 10
  ): Promise<Partner[]> {
    return this.repository
      .createQueryBuilder('partner')
      .where('partner.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('partner.deleted_at IS NULL')
      .andWhere('partner.is_active = true')
      .andWhere(
        '(partner.display_name ILIKE :query OR partner.code ILIKE :query OR partner.tax_id ILIKE :query)',
        { query: `%${query}%` }
      )
      .orderBy('partner.display_name', 'ASC')
      .take(limit)
      .getMany();
  }
}
