import { Repository } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Tenant, TenantStatus, User, UserStatus, Company, Role } from '../auth/entities/index.js';
import { PaginationParams, NotFoundError, ValidationError, ForbiddenError } from '../../shared/types/index.js';
import { logger } from '../../shared/utils/logger.js';

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

export interface CreateTenantDto {
  name: string;
  subdomain: string;
  plan?: string;
  maxUsers?: number;
  settings?: Record<string, any>;
}

export interface UpdateTenantDto {
  name?: string;
  plan?: string;
  maxUsers?: number;
  settings?: Record<string, any>;
}

export interface TenantStats {
  usersCount: number;
  companiesCount: number;
  rolesCount: number;
  activeUsersCount: number;
}

export interface TenantWithStats extends Tenant {
  stats?: TenantStats;
}

// ===== TenantsService Class =====

class TenantsService {
  private tenantRepository: Repository<Tenant>;
  private userRepository: Repository<User>;
  private companyRepository: Repository<Company>;
  private roleRepository: Repository<Role>;

  constructor() {
    this.tenantRepository = AppDataSource.getRepository(Tenant);
    this.userRepository = AppDataSource.getRepository(User);
    this.companyRepository = AppDataSource.getRepository(Company);
    this.roleRepository = AppDataSource.getRepository(Role);
  }

  /**
   * Get all tenants with pagination (super_admin only)
   */
  async findAll(
    params: PaginationParams,
    filter?: { status?: TenantStatus; search?: string }
  ): Promise<{ tenants: Tenant[]; total: number }> {
    try {
      const { page, limit, sortBy = 'name', sortOrder = 'asc' } = params;
      const skip = (page - 1) * limit;

      const queryBuilder = this.tenantRepository
        .createQueryBuilder('tenant')
        .where('tenant.deletedAt IS NULL')
        .orderBy(`tenant.${sortBy}`, sortOrder.toUpperCase() as 'ASC' | 'DESC')
        .skip(skip)
        .take(limit);

      // Apply filters
      if (filter?.status) {
        queryBuilder.andWhere('tenant.status = :status', { status: filter.status });
      }
      if (filter?.search) {
        queryBuilder.andWhere(
          '(tenant.name ILIKE :search OR tenant.subdomain ILIKE :search)',
          { search: `%${filter.search}%` }
        );
      }

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

      logger.debug('Tenants retrieved', { count: tenants.length, total });

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

  /**
   * Get tenant by ID
   */
  async findById(tenantId: string): Promise<TenantWithStats> {
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: undefined },
      });

      if (!tenant) {
        throw new NotFoundError('Tenant no encontrado');
      }

      // Get stats
      const stats = await this.getTenantStats(tenantId);

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

  /**
   * Get tenant by subdomain
   */
  async findBySubdomain(subdomain: string): Promise<Tenant | null> {
    try {
      return await this.tenantRepository.findOne({
        where: { subdomain, deletedAt: undefined },
      });
    } catch (error) {
      logger.error('Error finding tenant by subdomain', {
        error: (error as Error).message,
        subdomain,
      });
      throw error;
    }
  }

  /**
   * Get tenant statistics
   */
  async getTenantStats(tenantId: string): Promise<TenantStats> {
    try {
      const [usersCount, activeUsersCount, companiesCount, rolesCount] = await Promise.all([
        this.userRepository.count({
          where: { tenantId, deletedAt: undefined },
        }),
        this.userRepository.count({
          where: { tenantId, status: UserStatus.ACTIVE, deletedAt: undefined },
        }),
        this.companyRepository.count({
          where: { tenantId, deletedAt: undefined },
        }),
        this.roleRepository.count({
          where: { tenantId, deletedAt: undefined },
        }),
      ]);

      return {
        usersCount,
        activeUsersCount,
        companiesCount,
        rolesCount,
      };
    } catch (error) {
      logger.error('Error getting tenant stats', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Create a new tenant (super_admin only)
   */
  async create(data: CreateTenantDto, createdBy: string): Promise<Tenant> {
    try {
      // Validate subdomain uniqueness
      const existing = await this.findBySubdomain(data.subdomain);
      if (existing) {
        throw new ValidationError('Ya existe un tenant con este subdominio');
      }

      // Validate subdomain format (alphanumeric and hyphens only)
      if (!/^[a-z0-9-]+$/.test(data.subdomain)) {
        throw new ValidationError('El subdominio solo puede contener letras minúsculas, números y guiones');
      }

      // Generate schema name from subdomain
      const schemaName = `tenant_${data.subdomain.replace(/-/g, '_')}`;

      // Create tenant
      const tenant = this.tenantRepository.create({
        name: data.name,
        subdomain: data.subdomain,
        schemaName,
        status: TenantStatus.ACTIVE,
        plan: data.plan || 'basic',
        maxUsers: data.maxUsers || 10,
        settings: data.settings || {},
        createdBy,
      });

      await this.tenantRepository.save(tenant);

      logger.info('Tenant created', {
        tenantId: tenant.id,
        subdomain: tenant.subdomain,
        createdBy,
      });

      return tenant;
    } catch (error) {
      logger.error('Error creating tenant', {
        error: (error as Error).message,
        data,
      });
      throw error;
    }
  }

  /**
   * Update a tenant
   */
  async update(
    tenantId: string,
    data: UpdateTenantDto,
    updatedBy: string
  ): Promise<Tenant> {
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: undefined },
      });

      if (!tenant) {
        throw new NotFoundError('Tenant no encontrado');
      }

      // Update allowed fields
      if (data.name !== undefined) tenant.name = data.name;
      if (data.plan !== undefined) tenant.plan = data.plan;
      if (data.maxUsers !== undefined) tenant.maxUsers = data.maxUsers;
      if (data.settings !== undefined) {
        tenant.settings = { ...tenant.settings, ...data.settings };
      }

      tenant.updatedBy = updatedBy;
      tenant.updatedAt = new Date();

      await this.tenantRepository.save(tenant);

      logger.info('Tenant updated', {
        tenantId,
        updatedBy,
      });

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

  /**
   * Change tenant status
   */
  async changeStatus(
    tenantId: string,
    status: TenantStatus,
    updatedBy: string
  ): Promise<Tenant> {
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: undefined },
      });

      if (!tenant) {
        throw new NotFoundError('Tenant no encontrado');
      }

      tenant.status = status;
      tenant.updatedBy = updatedBy;
      tenant.updatedAt = new Date();

      await this.tenantRepository.save(tenant);

      logger.info('Tenant status changed', {
        tenantId,
        status,
        updatedBy,
      });

      return tenant;
    } catch (error) {
      logger.error('Error changing tenant status', {
        error: (error as Error).message,
        tenantId,
        status,
      });
      throw error;
    }
  }

  /**
   * Suspend a tenant
   */
  async suspend(tenantId: string, updatedBy: string): Promise<Tenant> {
    return this.changeStatus(tenantId, TenantStatus.SUSPENDED, updatedBy);
  }

  /**
   * Activate a tenant
   */
  async activate(tenantId: string, updatedBy: string): Promise<Tenant> {
    return this.changeStatus(tenantId, TenantStatus.ACTIVE, updatedBy);
  }

  /**
   * Soft delete a tenant
   */
  async delete(tenantId: string, deletedBy: string): Promise<void> {
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: undefined },
      });

      if (!tenant) {
        throw new NotFoundError('Tenant no encontrado');
      }

      // Check if tenant has active users
      const activeUsers = await this.userRepository.count({
        where: { tenantId, status: UserStatus.ACTIVE, deletedAt: undefined },
      });

      if (activeUsers > 0) {
        throw new ForbiddenError(
          `No se puede eliminar el tenant porque tiene ${activeUsers} usuario(s) activo(s). Primero desactive todos los usuarios.`
        );
      }

      // Soft delete
      tenant.deletedAt = new Date();
      tenant.deletedBy = deletedBy;
      tenant.status = TenantStatus.CANCELLED;

      await this.tenantRepository.save(tenant);

      logger.info('Tenant deleted', {
        tenantId,
        deletedBy,
      });
    } catch (error) {
      logger.error('Error deleting tenant', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get tenant settings
   */
  async getSettings(tenantId: string): Promise<Record<string, any>> {
    const tenant = await this.findById(tenantId);
    return tenant.settings || {};
  }

  /**
   * Update tenant settings (merge)
   */
  async updateSettings(
    tenantId: string,
    settings: Record<string, any>,
    updatedBy: string
  ): Promise<Record<string, any>> {
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: undefined },
      });

      if (!tenant) {
        throw new NotFoundError('Tenant no encontrado');
      }

      tenant.settings = { ...tenant.settings, ...settings };
      tenant.updatedBy = updatedBy;
      tenant.updatedAt = new Date();

      await this.tenantRepository.save(tenant);

      logger.info('Tenant settings updated', {
        tenantId,
        updatedBy,
      });

      return tenant.settings;
    } catch (error) {
      logger.error('Error updating tenant settings', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Check if tenant has reached user limit
   */
  async canAddUser(tenantId: string): Promise<{ allowed: boolean; reason?: string }> {
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: undefined },
      });

      if (!tenant) {
        return { allowed: false, reason: 'Tenant no encontrado' };
      }

      if (tenant.status !== TenantStatus.ACTIVE) {
        return { allowed: false, reason: 'Tenant no está activo' };
      }

      const currentUsers = await this.userRepository.count({
        where: { tenantId, deletedAt: undefined },
      });

      if (currentUsers >= tenant.maxUsers) {
        return {
          allowed: false,
          reason: `Se ha alcanzado el límite de usuarios (${tenant.maxUsers})`,
        };
      }

      return { allowed: true };
    } catch (error) {
      logger.error('Error checking user limit', {
        error: (error as Error).message,
        tenantId,
      });
      return { allowed: false, reason: 'Error verificando límite de usuarios' };
    }
  }
}

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

export const tenantsService = new TenantsService();
