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

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

export interface CreateCompanyDto {
  name: string;
  legalName?: string;
  taxId?: string;
  currencyId?: string;
  parentCompanyId?: string;
  settings?: Record<string, any>;
}

export interface UpdateCompanyDto {
  name?: string;
  legalName?: string | null;
  taxId?: string | null;
  currencyId?: string | null;
  parentCompanyId?: string | null;
  settings?: Record<string, any>;
}

export interface CompanyFilters {
  search?: string;
  parentCompanyId?: string;
  page?: number;
  limit?: number;
}

export interface CompanyWithRelations extends Company {
  currencyCode?: string;
  parentCompanyName?: string;
}

// ===== CompaniesService Class =====

class CompaniesService {
  private companyRepository: Repository<Company>;

  constructor() {
    this.companyRepository = AppDataSource.getRepository(Company);
  }

  /**
   * Get all companies for a tenant with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: CompanyFilters = {}
  ): Promise<{ data: CompanyWithRelations[]; total: number }> {
    try {
      const { search, parentCompanyId, page = 1, limit = 20 } = filters;
      const skip = (page - 1) * limit;

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

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

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

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

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

      // Map to include relation names
      const data: CompanyWithRelations[] = companies.map(company => ({
        ...company,
        parentCompanyName: company.parentCompany?.name,
      }));

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

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

  /**
   * Get company by ID
   */
  async findById(id: string, tenantId: string): Promise<CompanyWithRelations> {
    try {
      const company = await this.companyRepository
        .createQueryBuilder('company')
        .leftJoin('company.parentCompany', 'parentCompany')
        .addSelect(['parentCompany.name'])
        .where('company.id = :id', { id })
        .andWhere('company.tenantId = :tenantId', { tenantId })
        .andWhere('company.deletedAt IS NULL')
        .getOne();

      if (!company) {
        throw new NotFoundError('Empresa no encontrada');
      }

      return {
        ...company,
        parentCompanyName: company.parentCompany?.name,
      };
    } catch (error) {
      logger.error('Error finding company', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Create a new company
   */
  async create(
    dto: CreateCompanyDto,
    tenantId: string,
    userId: string
  ): Promise<Company> {
    try {
      // Validate unique tax_id within tenant
      if (dto.taxId) {
        const existing = await this.companyRepository.findOne({
          where: {
            tenantId,
            taxId: dto.taxId,
            deletedAt: IsNull(),
          },
        });

        if (existing) {
          throw new ValidationError('Ya existe una empresa con este RFC');
        }
      }

      // Validate parent company exists
      if (dto.parentCompanyId) {
        const parent = await this.companyRepository.findOne({
          where: {
            id: dto.parentCompanyId,
            tenantId,
            deletedAt: IsNull(),
          },
        });

        if (!parent) {
          throw new NotFoundError('Empresa matriz no encontrada');
        }
      }

      // Create company
      const company = this.companyRepository.create({
        tenantId,
        name: dto.name,
        legalName: dto.legalName || null,
        taxId: dto.taxId || null,
        currencyId: dto.currencyId || null,
        parentCompanyId: dto.parentCompanyId || null,
        settings: dto.settings || {},
        createdBy: userId,
      });

      await this.companyRepository.save(company);

      logger.info('Company created', {
        companyId: company.id,
        tenantId,
        name: company.name,
        createdBy: userId,
      });

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

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

      // Validate unique tax_id if changing
      if (dto.taxId !== undefined && dto.taxId !== existing.taxId) {
        if (dto.taxId) {
          const duplicate = await this.companyRepository.findOne({
            where: {
              tenantId,
              taxId: dto.taxId,
              deletedAt: IsNull(),
            },
          });

          if (duplicate && duplicate.id !== id) {
            throw new ValidationError('Ya existe una empresa con este RFC');
          }
        }
      }

      // Validate parent company (prevent self-reference and cycles)
      if (dto.parentCompanyId !== undefined && dto.parentCompanyId) {
        if (dto.parentCompanyId === id) {
          throw new ValidationError('Una empresa no puede ser su propia matriz');
        }

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

        if (!parent) {
          throw new NotFoundError('Empresa matriz no encontrada');
        }

        // Check for circular reference
        if (await this.wouldCreateCycle(id, dto.parentCompanyId, tenantId)) {
          throw new ValidationError('La asignación crearía una referencia circular');
        }
      }

      // Update allowed fields
      if (dto.name !== undefined) existing.name = dto.name;
      if (dto.legalName !== undefined) existing.legalName = dto.legalName;
      if (dto.taxId !== undefined) existing.taxId = dto.taxId;
      if (dto.currencyId !== undefined) existing.currencyId = dto.currencyId;
      if (dto.parentCompanyId !== undefined) existing.parentCompanyId = dto.parentCompanyId;
      if (dto.settings !== undefined) {
        existing.settings = { ...existing.settings, ...dto.settings };
      }

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

      await this.companyRepository.save(existing);

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

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

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

      // Check if company has child companies
      const childrenCount = await this.companyRepository.count({
        where: {
          parentCompanyId: id,
          tenantId,
          deletedAt: IsNull(),
        },
      });

      if (childrenCount > 0) {
        throw new ForbiddenError(
          'No se puede eliminar una empresa que tiene empresas subsidiarias'
        );
      }

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

      logger.info('Company deleted', {
        companyId: id,
        tenantId,
        deletedBy: userId,
      });
    } catch (error) {
      logger.error('Error deleting company', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get users assigned to a company
   */
  async getUsers(companyId: string, tenantId: string): Promise<any[]> {
    try {
      await this.findById(companyId, tenantId);

      // Using raw query for user_companies junction table
      const users = await this.companyRepository.query(
        `SELECT u.id, u.email, u.full_name, u.status, uc.is_default, uc.assigned_at
         FROM auth.users u
         INNER JOIN auth.user_companies uc ON u.id = uc.user_id
         WHERE uc.company_id = $1 AND u.tenant_id = $2 AND u.deleted_at IS NULL
         ORDER BY u.full_name`,
        [companyId, tenantId]
      );

      return users;
    } catch (error) {
      logger.error('Error getting company users', {
        error: (error as Error).message,
        companyId,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get child companies (subsidiaries)
   */
  async getSubsidiaries(companyId: string, tenantId: string): Promise<Company[]> {
    try {
      await this.findById(companyId, tenantId);

      return await this.companyRepository.find({
        where: {
          parentCompanyId: companyId,
          tenantId,
          deletedAt: IsNull(),
        },
        order: { name: 'ASC' },
      });
    } catch (error) {
      logger.error('Error getting subsidiaries', {
        error: (error as Error).message,
        companyId,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get full company hierarchy (tree structure)
   */
  async getHierarchy(tenantId: string): Promise<any[]> {
    try {
      // Get all companies
      const companies = await this.companyRepository.find({
        where: { tenantId, deletedAt: IsNull() },
        order: { name: 'ASC' },
      });

      // Build tree structure
      const companyMap = new Map<string, any>();
      const roots: any[] = [];

      // First pass: create map
      for (const company of companies) {
        companyMap.set(company.id, {
          ...company,
          children: [],
        });
      }

      // Second pass: build tree
      for (const company of companies) {
        const node = companyMap.get(company.id);
        if (company.parentCompanyId && companyMap.has(company.parentCompanyId)) {
          companyMap.get(company.parentCompanyId).children.push(node);
        } else {
          roots.push(node);
        }
      }

      return roots;
    } catch (error) {
      logger.error('Error getting company hierarchy', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Check if assigning a parent would create a circular reference
   */
  private async wouldCreateCycle(
    companyId: string,
    newParentId: string,
    tenantId: string
  ): Promise<boolean> {
    let currentId: string | null = newParentId;
    const visited = new Set<string>();

    while (currentId) {
      if (visited.has(currentId)) {
        return true; // Found a cycle
      }
      if (currentId === companyId) {
        return true; // Would create a cycle
      }

      visited.add(currentId);

      const parent = await this.companyRepository.findOne({
        where: { id: currentId, tenantId, deletedAt: IsNull() },
        select: ['parentCompanyId'],
      });

      currentId = parent?.parentCompanyId || null;
    }

    return false;
  }
}

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

export const companiesService = new CompaniesService();
