import { Repository, IsNull } 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';

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

export interface CreatePartnerDto {
  name: string;
  legalName?: string;
  partnerType?: PartnerType;
  isCustomer?: boolean;
  isSupplier?: boolean;
  isEmployee?: boolean;
  isCompany?: boolean;
  email?: string;
  phone?: string;
  mobile?: string;
  website?: string;
  taxId?: string;
  companyId?: string;
  parentId?: string;
  currencyId?: string;
  notes?: string;
}

export interface UpdatePartnerDto {
  name?: string;
  legalName?: string | null;
  isCustomer?: boolean;
  isSupplier?: boolean;
  isEmployee?: boolean;
  email?: string | null;
  phone?: string | null;
  mobile?: string | null;
  website?: string | null;
  taxId?: string | null;
  companyId?: string | null;
  parentId?: string | null;
  currencyId?: string | null;
  notes?: string | null;
  active?: boolean;
}

export interface PartnerFilters {
  search?: string;
  isCustomer?: boolean;
  isSupplier?: boolean;
  isEmployee?: boolean;
  companyId?: string;
  active?: boolean;
  page?: number;
  limit?: number;
}

export interface PartnerWithRelations extends Partner {
  companyName?: string;
  currencyCode?: string;
  parentName?: string;
}

// ===== 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: PartnerWithRelations[]; total: number }> {
    try {
      const { search, isCustomer, isSupplier, isEmployee, companyId, active, page = 1, limit = 20 } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.partnerRepository
        .createQueryBuilder('partner')
        .leftJoin('partner.company', 'company')
        .addSelect(['company.name'])
        .leftJoin('partner.parentPartner', 'parentPartner')
        .addSelect(['parentPartner.name'])
        .where('partner.tenantId = :tenantId', { tenantId })
        .andWhere('partner.deletedAt IS NULL');

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

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

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

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

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

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

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

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

      // Map to include relation names
      const data: PartnerWithRelations[] = partners.map(partner => ({
        ...partner,
        companyName: partner.company?.name,
        parentName: partner.parentPartner?.name,
      }));

      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<PartnerWithRelations> {
    try {
      const partner = await this.partnerRepository
        .createQueryBuilder('partner')
        .leftJoin('partner.company', 'company')
        .addSelect(['company.name'])
        .leftJoin('partner.parentPartner', 'parentPartner')
        .addSelect(['parentPartner.name'])
        .where('partner.id = :id', { id })
        .andWhere('partner.tenantId = :tenantId', { tenantId })
        .andWhere('partner.deletedAt IS NULL')
        .getOne();

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

      return {
        ...partner,
        companyName: partner.company?.name,
        parentName: partner.parentPartner?.name,
      };
    } 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 {
      // Validate parent partner exists
      if (dto.parentId) {
        const parent = await this.partnerRepository.findOne({
          where: {
            id: dto.parentId,
            tenantId,
            deletedAt: IsNull(),
          },
        });

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

      // Create partner
      const partner = this.partnerRepository.create({
        tenantId,
        name: dto.name,
        legalName: dto.legalName || null,
        partnerType: dto.partnerType || 'person',
        isCustomer: dto.isCustomer || false,
        isSupplier: dto.isSupplier || false,
        isEmployee: dto.isEmployee || false,
        isCompany: dto.isCompany || false,
        email: dto.email?.toLowerCase() || null,
        phone: dto.phone || null,
        mobile: dto.mobile || null,
        website: dto.website || null,
        taxId: dto.taxId || null,
        companyId: dto.companyId || null,
        parentId: dto.parentId || null,
        currencyId: dto.currencyId || null,
        notes: dto.notes || null,
        createdBy: userId,
      });

      await this.partnerRepository.save(partner);

      logger.info('Partner created', {
        partnerId: partner.id,
        tenantId,
        name: partner.name,
        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);

      // Validate parent partner (prevent self-reference)
      if (dto.parentId !== undefined && dto.parentId) {
        if (dto.parentId === id) {
          throw new ValidationError('Un contacto no puede ser su propio padre');
        }

        const parent = await this.partnerRepository.findOne({
          where: {
            id: dto.parentId,
            tenantId,
            deletedAt: IsNull(),
          },
        });

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

      // Update allowed fields
      if (dto.name !== undefined) existing.name = dto.name;
      if (dto.legalName !== undefined) existing.legalName = dto.legalName;
      if (dto.isCustomer !== undefined) existing.isCustomer = dto.isCustomer;
      if (dto.isSupplier !== undefined) existing.isSupplier = dto.isSupplier;
      if (dto.isEmployee !== undefined) existing.isEmployee = dto.isEmployee;
      if (dto.email !== undefined) existing.email = dto.email?.toLowerCase() || null;
      if (dto.phone !== undefined) existing.phone = dto.phone;
      if (dto.mobile !== undefined) existing.mobile = dto.mobile;
      if (dto.website !== undefined) existing.website = dto.website;
      if (dto.taxId !== undefined) existing.taxId = dto.taxId;
      if (dto.companyId !== undefined) existing.companyId = dto.companyId;
      if (dto.parentId !== undefined) existing.parentId = dto.parentId;
      if (dto.currencyId !== undefined) existing.currencyId = dto.currencyId;
      if (dto.notes !== undefined) existing.notes = dto.notes;
      if (dto.active !== undefined) existing.active = dto.active;

      existing.updatedBy = userId;
      existing.updatedAt = new Date();

      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 {
      await this.findById(id, tenantId);

      // Check if has child partners
      const childrenCount = await this.partnerRepository.count({
        where: {
          parentId: id,
          tenantId,
          deletedAt: IsNull(),
        },
      });

      if (childrenCount > 0) {
        throw new ForbiddenError(
          'No se puede eliminar un contacto que tiene contactos relacionados'
        );
      }

      // Soft delete
      await this.partnerRepository.update(
        { id, tenantId },
        {
          deletedAt: new Date(),
          deletedBy: userId,
          active: false,
        }
      );

      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, 'isCustomer'>
  ): Promise<{ data: PartnerWithRelations[]; total: number }> {
    return this.findAll(tenantId, { ...filters, isCustomer: true });
  }

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

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

export const partnersService = new PartnersService();
