import { Repository, IsNull } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Tenant, TenantStatus, TenantPlan, User, UserStatus, Company, Role } from '../auth/entities/index.js';
import { TenantSettings, TenantLanguage, TenantCurrency, DateFormat } from './entities/index.js';
import { PaginationParams, NotFoundError, ValidationError, ForbiddenError } from '../../shared/types/index.js';
import { logger } from '../../shared/utils/logger.js';
import { CreateTenantDto, UpdateTenantDto, UpdateTenantSettingsDto } from './dto/index.js';

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

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

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

export interface TenantListFilter {
  status?: TenantStatus;
  plan?: TenantPlan;
  search?: string;
}

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

class TenantsService {
  private tenantRepository: Repository<Tenant>;
  private tenantSettingsRepository: Repository<TenantSettings>;
  private userRepository: Repository<User>;
  private companyRepository: Repository<Company>;
  private roleRepository: Repository<Role>;
  private initialized = false;

  constructor() {
    // Repositories will be initialized lazily
  }

  /**
   * Initialize repositories (called lazily on first use)
   */
  private ensureInitialized(): void {
    if (!this.initialized && AppDataSource.isInitialized) {
      this.tenantRepository = AppDataSource.getRepository(Tenant);
      this.tenantSettingsRepository = AppDataSource.getRepository(TenantSettings);
      this.userRepository = AppDataSource.getRepository(User);
      this.companyRepository = AppDataSource.getRepository(Company);
      this.roleRepository = AppDataSource.getRepository(Role);
      this.initialized = true;
    }
  }

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

      // Validate sortBy to prevent SQL injection
      const allowedSortFields = ['name', 'subdomain', 'status', 'plan', 'createdAt', 'maxUsers'];
      const safeSortBy = allowedSortFields.includes(sortBy) ? sortBy : 'name';

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

      // Apply filters
      if (filter?.status) {
        queryBuilder.andWhere('tenant.status = :status', { status: filter.status });
      }
      if (filter?.plan) {
        queryBuilder.andWhere('tenant.plan = :plan', { plan: filter.plan });
      }
      if (filter?.search) {
        queryBuilder.andWhere(
          '(tenant.name ILIKE :search OR tenant.subdomain ILIKE :search OR tenant.contactEmail 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, includeSettings = false): Promise<TenantWithStats> {
    this.ensureInitialized();
    try {
      const queryBuilder = this.tenantRepository
        .createQueryBuilder('tenant')
        .where('tenant.id = :tenantId', { tenantId })
        .andWhere('tenant.deletedAt IS NULL');

      if (includeSettings) {
        queryBuilder.leftJoinAndSelect('tenant.tenantSettings', 'settings');
      }

      const tenant = await queryBuilder.getOne();

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

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

      return { ...tenant, stats };
    } catch (error) {
      if (error instanceof NotFoundError) throw 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> {
    this.ensureInitialized();
    try {
      return await this.tenantRepository.findOne({
        where: { subdomain, deletedAt: IsNull() },
      });
    } catch (error) {
      logger.error('Error finding tenant by subdomain', {
        error: (error as Error).message,
        subdomain,
      });
      throw error;
    }
  }

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

  /**
   * Get tenant statistics
   */
  async getTenantStats(tenantId: string): Promise<TenantStats> {
    this.ensureInitialized();
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: IsNull() },
        select: ['maxStorageMb', 'currentStorageMb'],
      });

      const [usersCount, activeUsersCount, companiesCount, rolesCount] = await Promise.all([
        this.userRepository.count({
          where: { tenantId, deletedAt: IsNull() },
        }),
        this.userRepository.count({
          where: { tenantId, status: UserStatus.ACTIVE, deletedAt: IsNull() },
        }),
        this.companyRepository.count({
          where: { tenantId, deletedAt: IsNull() },
        }),
        this.roleRepository.count({
          where: { tenantId, deletedAt: IsNull() },
        }),
      ]);

      return {
        usersCount,
        activeUsersCount,
        companiesCount,
        rolesCount,
        storageUsedMb: tenant?.currentStorageMb || 0,
        storageAvailableMb: (tenant?.maxStorageMb || 0) - (tenant?.currentStorageMb || 0),
      };
    } 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> {
    this.ensureInitialized();
    const queryRunner = AppDataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // Validate subdomain uniqueness
      const existing = await this.findBySubdomain(data.subdomain);
      if (existing) {
        throw new ValidationError('Ya existe un tenant con este subdominio');
      }

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

      // Calculate trial end date if specified
      let trialEndsAt: Date | null = null;
      if (data.trialDays && data.trialDays > 0) {
        trialEndsAt = new Date();
        trialEndsAt.setDate(trialEndsAt.getDate() + data.trialDays);
      }

      // Create tenant
      const tenant = queryRunner.manager.create(Tenant, {
        name: data.name,
        subdomain: data.subdomain,
        schemaName,
        status: trialEndsAt ? TenantStatus.TRIAL : TenantStatus.ACTIVE,
        plan: data.plan || TenantPlan.BASIC,
        maxUsers: data.maxUsers || 10,
        maxStorageMb: data.maxStorageMb || 1024,
        contactEmail: data.contactEmail,
        contactPhone: data.contactPhone,
        billingEmail: data.billingEmail,
        taxId: data.taxId,
        customDomain: data.customDomain,
        trialEndsAt,
        settings: {},
        metadata: data.metadata || {},
        createdBy,
      });

      await queryRunner.manager.save(tenant);

      // Create default tenant settings
      const settingsData = data.settings || {};
      const tenantSettings = queryRunner.manager.create(TenantSettings, {
        tenantId: tenant.id,
        defaultLanguage: settingsData.defaultLanguage || TenantLanguage.ES,
        defaultTimezone: settingsData.defaultTimezone || 'America/Mexico_City',
        defaultCurrency: settingsData.defaultCurrency || TenantCurrency.MXN,
        dateFormat: settingsData.dateFormat || DateFormat.DD_MM_YYYY,
        logoUrl: settingsData.logoUrl || null,
        primaryColor: settingsData.primaryColor || '#1976D2',
        secondaryColor: settingsData.secondaryColor || '#424242',
        require2fa: settingsData.require2fa || false,
        sessionTimeoutMinutes: settingsData.sessionTimeoutMinutes || 480,
        featureFlags: settingsData.featureFlags || {},
      });

      await queryRunner.manager.save(tenantSettings);

      await queryRunner.commitTransaction();

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

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

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

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

      // Update allowed fields
      if (data.name !== undefined) tenant.name = data.name;
      if (data.status !== undefined) tenant.status = data.status;
      if (data.plan !== undefined) tenant.plan = data.plan as any;
      if (data.maxUsers !== undefined) tenant.maxUsers = data.maxUsers;
      if (data.maxStorageMb !== undefined) tenant.maxStorageMb = data.maxStorageMb;
      if (data.contactEmail !== undefined) tenant.contactEmail = data.contactEmail;
      if (data.contactPhone !== undefined) tenant.contactPhone = data.contactPhone;
      if (data.billingEmail !== undefined) tenant.billingEmail = data.billingEmail;
      if (data.taxId !== undefined) tenant.taxId = data.taxId;
      if (data.customDomain !== undefined) tenant.customDomain = data.customDomain;
      if (data.trialEndsAt !== undefined) tenant.trialEndsAt = data.trialEndsAt;
      if (data.subscriptionEndsAt !== undefined) tenant.subscriptionEndsAt = data.subscriptionEndsAt;
      if (data.metadata !== undefined) {
        tenant.metadata = { ...tenant.metadata, ...data.metadata };
      }

      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) {
      if (error instanceof NotFoundError) throw 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> {
    this.ensureInitialized();
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: IsNull() },
      });

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

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

      await this.tenantRepository.save(tenant);

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

      return tenant;
    } catch (error) {
      if (error instanceof NotFoundError) throw 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> {
    this.ensureInitialized();
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: IsNull() },
      });

      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: IsNull() },
      });

      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) {
      if (error instanceof NotFoundError || error instanceof ForbiddenError) throw error;
      logger.error('Error deleting tenant', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get tenant settings
   */
  async getSettings(tenantId: string): Promise<TenantSettings> {
    this.ensureInitialized();
    try {
      // First verify tenant exists
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: IsNull() },
      });

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

      // Get settings
      let settings = await this.tenantSettingsRepository.findOne({
        where: { tenantId },
      });

      // Create default settings if not exist
      if (!settings) {
        settings = this.tenantSettingsRepository.create({
          tenantId,
          defaultLanguage: TenantLanguage.ES,
          defaultTimezone: 'America/Mexico_City',
          defaultCurrency: TenantCurrency.MXN,
          dateFormat: DateFormat.DD_MM_YYYY,
          primaryColor: '#1976D2',
          secondaryColor: '#424242',
          featureFlags: {},
          customConfig: {},
          oauthConfig: {},
        });
        await this.tenantSettingsRepository.save(settings);
      }

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

  /**
   * Update tenant settings
   */
  async updateSettings(
    tenantId: string,
    data: UpdateTenantSettingsDto,
    updatedBy: string
  ): Promise<TenantSettings> {
    this.ensureInitialized();
    try {
      // Verify tenant exists
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: IsNull() },
      });

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

      // Get or create settings
      let settings = await this.tenantSettingsRepository.findOne({
        where: { tenantId },
      });

      if (!settings) {
        settings = this.tenantSettingsRepository.create({ tenantId });
      }

      // Update fields
      const updateableFields: (keyof UpdateTenantSettingsDto)[] = [
        'defaultLanguage', 'defaultTimezone', 'defaultCurrency', 'dateFormat',
        'logoUrl', 'faviconUrl', 'primaryColor', 'secondaryColor',
        'require2fa', 'sessionTimeoutMinutes', 'passwordExpiryDays',
        'minPasswordLength', 'requireUppercase', 'requireNumbers',
        'requireSpecialChars', 'maxLoginAttempts', 'lockoutDurationMinutes',
        'emailNotificationsEnabled', 'smsNotificationsEnabled', 'pushNotificationsEnabled',
        'smtpConfig', 'oauthConfig',
      ];

      for (const field of updateableFields) {
        if (data[field] !== undefined) {
          (settings as any)[field] = data[field];
        }
      }

      // Merge objects for feature flags and custom config
      if (data.featureFlags !== undefined) {
        settings.featureFlags = { ...settings.featureFlags, ...data.featureFlags };
      }
      if (data.customConfig !== undefined) {
        settings.customConfig = { ...settings.customConfig, ...data.customConfig };
      }

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

      await this.tenantSettingsRepository.save(settings);

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

      return settings;
    } catch (error) {
      if (error instanceof NotFoundError) throw 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; currentUsers?: number; maxUsers?: number }> {
    this.ensureInitialized();
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: IsNull() },
      });

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

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

      // Check trial expiration
      if (tenant.status === TenantStatus.TRIAL && tenant.trialEndsAt) {
        if (new Date() > tenant.trialEndsAt) {
          return { allowed: false, reason: 'El periodo de prueba ha expirado' };
        }
      }

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

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

      return { allowed: true, currentUsers, maxUsers: tenant.maxUsers };
    } catch (error) {
      logger.error('Error checking user limit', {
        error: (error as Error).message,
        tenantId,
      });
      return { allowed: false, reason: 'Error verificando limite de usuarios' };
    }
  }

  /**
   * Check if tenant has available storage
   */
  async canUseStorage(tenantId: string, requiredMb: number): Promise<{ allowed: boolean; reason?: string }> {
    this.ensureInitialized();
    try {
      const tenant = await this.tenantRepository.findOne({
        where: { id: tenantId, deletedAt: IsNull() },
        select: ['status', 'maxStorageMb', 'currentStorageMb'],
      });

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

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

      const availableStorage = tenant.maxStorageMb - tenant.currentStorageMb;
      if (requiredMb > availableStorage) {
        return {
          allowed: false,
          reason: `Almacenamiento insuficiente. Disponible: ${availableStorage}MB, Requerido: ${requiredMb}MB`,
        };
      }

      return { allowed: true };
    } catch (error) {
      logger.error('Error checking storage', {
        error: (error as Error).message,
        tenantId,
      });
      return { allowed: false, reason: 'Error verificando almacenamiento' };
    }
  }

  /**
   * Update storage usage for a tenant
   */
  async updateStorageUsage(tenantId: string, deltaBytes: number): Promise<void> {
    this.ensureInitialized();
    try {
      const deltaMb = Math.ceil(deltaBytes / (1024 * 1024));

      await this.tenantRepository
        .createQueryBuilder()
        .update(Tenant)
        .set({
          currentStorageMb: () => `GREATEST(0, current_storage_mb + ${deltaMb})`,
        })
        .where('id = :tenantId', { tenantId })
        .execute();

      logger.debug('Tenant storage updated', { tenantId, deltaMb });
    } catch (error) {
      logger.error('Error updating storage usage', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }
}

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

export const tenantsService = new TenantsService();

// Re-export types from DTOs for backward compatibility
export type { CreateTenantDto, UpdateTenantDto, UpdateTenantSettingsDto } from './dto/index.js';
