import { Repository, IsNull, Like } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Partner, PartnerType } from './entities/index.js';
import { NotFoundError, ValidationError, ForbiddenError } from '../../shared/types/index.js';
import { logger } from '../../shared/utils/logger.js';

// Re-export PartnerType for controller use
export type { PartnerType };

// ===== Interfaces =====

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

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

export interface PartnerFilters {
  search?: string;
  partnerType?: PartnerType;
  category?: string;
  isActive?: boolean;
  isVerified?: boolean;
  page?: number;
  limit?: number;
}

export interface PartnerWithRelations extends Partner {
  // Add computed fields if needed
}

// ===== PartnersService Class =====

class PartnersService {
  private partnerRepository: Repository<Partner>;

  constructor() {
    this.partnerRepository = AppDataSource.getRepository(Partner);
  }

  /**
   * Get all partners for a tenant with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: PartnerFilters = {}
  ): Promise<{ data: Partner[]; total: number }> {
    try {
      const { search, partnerType, category, isActive, isVerified, page = 1, limit = 20 } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.partnerRepository
        .createQueryBuilder('partner')
        .where('partner.tenantId = :tenantId', { tenantId })
        .andWhere('partner.deletedAt IS NULL');

      // Apply search filter
      if (search) {
        queryBuilder.andWhere(
          '(partner.displayName ILIKE :search OR partner.legalName ILIKE :search OR partner.email ILIKE :search OR partner.taxId ILIKE :search OR partner.code ILIKE :search)',
          { search: `%${search}%` }
        );
      }

      // Filter by partner type
      if (partnerType !== undefined) {
        queryBuilder.andWhere('partner.partnerType = :partnerType', { partnerType });
      }

      // Filter by category
      if (category) {
        queryBuilder.andWhere('partner.category = :category', { category });
      }

      // Filter by active status
      if (isActive !== undefined) {
        queryBuilder.andWhere('partner.isActive = :isActive', { isActive });
      }

      // Filter by verified status
      if (isVerified !== undefined) {
        queryBuilder.andWhere('partner.isVerified = :isVerified', { isVerified });
      }

      // Get total count
      const total = await queryBuilder.getCount();

      // Get paginated results
      const data = await queryBuilder
        .orderBy('partner.displayName', 'ASC')
        .skip(skip)
        .take(limit)
        .getMany();

      logger.debug('Partners retrieved', { tenantId, count: data.length, total });

      return { data, total };
    } catch (error) {
      logger.error('Error retrieving partners', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get partner by ID
   */
  async findById(id: string, tenantId: string): Promise<Partner> {
    try {
      const partner = await this.partnerRepository.findOne({
        where: {
          id,
          tenantId,
          deletedAt: IsNull(),
        },
      });

      if (!partner) {
        throw new NotFoundError('Contacto no encontrado');
      }

      return partner;
    } catch (error) {
      logger.error('Error finding partner', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Create a new partner
   */
  async create(
    dto: CreatePartnerDto,
    tenantId: string,
    userId: string
  ): Promise<Partner> {
    try {
      // Check if code already exists
      const existing = await this.partnerRepository.findOne({
        where: { code: dto.code, tenantId },
      });

      if (existing) {
        throw new ValidationError('Ya existe un contacto con este código');
      }

      // Create partner - only include defined fields
      const partnerData: Partial<Partner> = {
        tenantId,
        code: dto.code,
        displayName: dto.displayName,
        partnerType: dto.partnerType || 'customer',
        paymentTermDays: dto.paymentTermDays ?? 0,
        creditLimit: dto.creditLimit ?? 0,
        discountPercent: dto.discountPercent ?? 0,
        tags: dto.tags || [],
        isActive: true,
        isVerified: false,
        createdBy: userId,
      };

      // Add optional fields only if defined
      if (dto.legalName) partnerData.legalName = dto.legalName;
      if (dto.email) partnerData.email = dto.email.toLowerCase();
      if (dto.phone) partnerData.phone = dto.phone;
      if (dto.mobile) partnerData.mobile = dto.mobile;
      if (dto.website) partnerData.website = dto.website;
      if (dto.taxId) partnerData.taxId = dto.taxId;
      if (dto.taxRegime) partnerData.taxRegime = dto.taxRegime;
      if (dto.cfdiUse) partnerData.cfdiUse = dto.cfdiUse;
      if (dto.priceListId) partnerData.priceListId = dto.priceListId;
      if (dto.category) partnerData.category = dto.category;
      if (dto.notes) partnerData.notes = dto.notes;
      if (dto.salesRepId) partnerData.salesRepId = dto.salesRepId;

      const partner = this.partnerRepository.create(partnerData);

      await this.partnerRepository.save(partner);

      logger.info('Partner created', {
        partnerId: partner.id,
        tenantId,
        code: partner.code,
        displayName: partner.displayName,
        createdBy: userId,
      });

      return partner;
    } catch (error) {
      logger.error('Error creating partner', {
        error: (error as Error).message,
        tenantId,
        dto,
      });
      throw error;
    }
  }

  /**
   * Update a partner
   */
  async update(
    id: string,
    dto: UpdatePartnerDto,
    tenantId: string,
    userId: string
  ): Promise<Partner> {
    try {
      const existing = await this.findById(id, tenantId);

      // Update allowed fields
      if (dto.displayName !== undefined) existing.displayName = dto.displayName;
      if (dto.legalName !== undefined) existing.legalName = dto.legalName as string;
      if (dto.partnerType !== undefined) existing.partnerType = dto.partnerType;
      if (dto.email !== undefined) existing.email = dto.email?.toLowerCase() || null as any;
      if (dto.phone !== undefined) existing.phone = dto.phone as string;
      if (dto.mobile !== undefined) existing.mobile = dto.mobile as string;
      if (dto.website !== undefined) existing.website = dto.website as string;
      if (dto.taxId !== undefined) existing.taxId = dto.taxId as string;
      if (dto.taxRegime !== undefined) existing.taxRegime = dto.taxRegime as string;
      if (dto.cfdiUse !== undefined) existing.cfdiUse = dto.cfdiUse as string;
      if (dto.paymentTermDays !== undefined) existing.paymentTermDays = dto.paymentTermDays;
      if (dto.creditLimit !== undefined) existing.creditLimit = dto.creditLimit;
      if (dto.priceListId !== undefined) existing.priceListId = dto.priceListId as string;
      if (dto.discountPercent !== undefined) existing.discountPercent = dto.discountPercent;
      if (dto.category !== undefined) existing.category = dto.category as string;
      if (dto.tags !== undefined) existing.tags = dto.tags;
      if (dto.notes !== undefined) existing.notes = dto.notes as string;
      if (dto.isActive !== undefined) existing.isActive = dto.isActive;
      if (dto.isVerified !== undefined) existing.isVerified = dto.isVerified;
      if (dto.salesRepId !== undefined) existing.salesRepId = dto.salesRepId as string;

      existing.updatedBy = userId;

      await this.partnerRepository.save(existing);

      logger.info('Partner updated', {
        partnerId: id,
        tenantId,
        updatedBy: userId,
      });

      return await this.findById(id, tenantId);
    } catch (error) {
      logger.error('Error updating partner', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Soft delete a partner
   */
  async delete(id: string, tenantId: string, userId: string): Promise<void> {
    try {
      const partner = await this.findById(id, tenantId);

      // Soft delete using the deletedAt column
      partner.deletedAt = new Date();
      partner.isActive = false;

      await this.partnerRepository.save(partner);

      logger.info('Partner deleted', {
        partnerId: id,
        tenantId,
        deletedBy: userId,
      });
    } catch (error) {
      logger.error('Error deleting partner', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get customers only
   */
  async findCustomers(
    tenantId: string,
    filters: Omit<PartnerFilters, 'partnerType'>
  ): Promise<{ data: Partner[]; total: number }> {
    return this.findAll(tenantId, { ...filters, partnerType: 'customer' });
  }

  /**
   * Get suppliers only
   */
  async findSuppliers(
    tenantId: string,
    filters: Omit<PartnerFilters, 'partnerType'>
  ): Promise<{ data: Partner[]; total: number }> {
    return this.findAll(tenantId, { ...filters, partnerType: 'supplier' });
  }
}

// ===== Export Singleton Instance =====

export const partnersService = new PartnersService();
