/**
 * Plan Limits Service
 *
 * Service for managing plan limits and usage validation
 */

import { Repository, DataSource } from 'typeorm';
import { PlanLimit, LimitType, SubscriptionPlan, TenantSubscription, UsageTracking } from '../entities/index.js';
import { logger } from '../../../shared/utils/logger.js';

export interface CreatePlanLimitDto {
  planId: string;
  limitKey: string;
  limitName: string;
  limitValue: number;
  limitType?: LimitType;
  allowOverage?: boolean;
  overageUnitPrice?: number;
  overageCurrency?: string;
}

export interface UpdatePlanLimitDto {
  limitName?: string;
  limitValue?: number;
  allowOverage?: boolean;
  overageUnitPrice?: number;
}

export interface UsageCheckResult {
  allowed: boolean;
  currentUsage: number;
  limit: number;
  remaining: number;
  overageAllowed: boolean;
  overageUnits?: number;
  overageCost?: number;
  message: string;
}

export class PlanLimitsService {
  private limitRepository: Repository<PlanLimit>;
  private planRepository: Repository<SubscriptionPlan>;
  private subscriptionRepository: Repository<TenantSubscription>;
  private usageRepository: Repository<UsageTracking>;

  constructor(private dataSource: DataSource) {
    this.limitRepository = dataSource.getRepository(PlanLimit);
    this.planRepository = dataSource.getRepository(SubscriptionPlan);
    this.subscriptionRepository = dataSource.getRepository(TenantSubscription);
    this.usageRepository = dataSource.getRepository(UsageTracking);
  }

  /**
   * Create a new plan limit
   */
  async create(dto: CreatePlanLimitDto): Promise<PlanLimit> {
    // Verify plan exists
    const plan = await this.planRepository.findOne({ where: { id: dto.planId } });
    if (!plan) {
      throw new Error('Plan not found');
    }

    // Check for duplicate limit key
    const existing = await this.limitRepository.findOne({
      where: { planId: dto.planId, limitKey: dto.limitKey },
    });
    if (existing) {
      throw new Error(`Limit ${dto.limitKey} already exists for this plan`);
    }

    const limit = this.limitRepository.create({
      planId: dto.planId,
      limitKey: dto.limitKey,
      limitName: dto.limitName,
      limitValue: dto.limitValue,
      limitType: dto.limitType || 'monthly',
      allowOverage: dto.allowOverage || false,
      overageUnitPrice: dto.overageUnitPrice || 0,
      overageCurrency: dto.overageCurrency || 'MXN',
    });

    const saved = await this.limitRepository.save(limit);

    logger.info('Plan limit created', {
      limitId: saved.id,
      planId: dto.planId,
      limitKey: dto.limitKey,
    });

    return saved;
  }

  /**
   * Find all limits for a plan
   */
  async findByPlan(planId: string): Promise<PlanLimit[]> {
    return this.limitRepository.find({
      where: { planId },
      order: { limitKey: 'ASC' },
    });
  }

  /**
   * Find a specific limit by key
   */
  async findByKey(planId: string, limitKey: string): Promise<PlanLimit | null> {
    return this.limitRepository.findOne({
      where: { planId, limitKey },
    });
  }

  /**
   * Update a plan limit
   */
  async update(id: string, dto: UpdatePlanLimitDto): Promise<PlanLimit> {
    const limit = await this.limitRepository.findOne({ where: { id } });
    if (!limit) {
      throw new Error('Limit not found');
    }

    if (dto.limitName !== undefined) limit.limitName = dto.limitName;
    if (dto.limitValue !== undefined) limit.limitValue = dto.limitValue;
    if (dto.allowOverage !== undefined) limit.allowOverage = dto.allowOverage;
    if (dto.overageUnitPrice !== undefined) limit.overageUnitPrice = dto.overageUnitPrice;

    return this.limitRepository.save(limit);
  }

  /**
   * Delete a plan limit
   */
  async delete(id: string): Promise<void> {
    const limit = await this.limitRepository.findOne({ where: { id } });
    if (!limit) {
      throw new Error('Limit not found');
    }

    await this.limitRepository.remove(limit);

    logger.info('Plan limit deleted', { limitId: id });
  }

  /**
   * Get tenant's current plan limits
   */
  async getTenantLimits(tenantId: string): Promise<PlanLimit[]> {
    const subscription = await this.subscriptionRepository.findOne({
      where: { tenantId, status: 'active' },
    });

    if (!subscription) {
      // Return free plan limits by default
      const freePlan = await this.planRepository.findOne({
        where: { code: 'FREE' },
      });
      if (freePlan) {
        return this.findByPlan(freePlan.id);
      }
      return [];
    }

    return this.findByPlan(subscription.planId);
  }

  /**
   * Get a specific limit for a tenant
   */
  async getTenantLimit(tenantId: string, limitKey: string): Promise<number> {
    const limits = await this.getTenantLimits(tenantId);
    const limit = limits.find((l) => l.limitKey === limitKey);
    return limit?.limitValue || 0;
  }

  /**
   * Check if tenant can use a resource (within limits)
   */
  async checkUsage(
    tenantId: string,
    limitKey: string,
    currentUsage: number,
    requestedUnits: number = 1
  ): Promise<UsageCheckResult> {
    const limits = await this.getTenantLimits(tenantId);
    const limit = limits.find((l) => l.limitKey === limitKey);

    if (!limit) {
      // No limit defined = unlimited
      return {
        allowed: true,
        currentUsage,
        limit: -1,
        remaining: -1,
        overageAllowed: false,
        message: 'Sin límite definido',
      };
    }

    const remaining = limit.limitValue - currentUsage;
    const wouldExceed = currentUsage + requestedUnits > limit.limitValue;

    if (!wouldExceed) {
      return {
        allowed: true,
        currentUsage,
        limit: limit.limitValue,
        remaining: remaining - requestedUnits,
        overageAllowed: limit.allowOverage,
        message: 'Dentro del límite',
      };
    }

    // Would exceed limit
    if (limit.allowOverage) {
      const overageUnits = currentUsage + requestedUnits - limit.limitValue;
      const overageCost = overageUnits * Number(limit.overageUnitPrice);

      return {
        allowed: true,
        currentUsage,
        limit: limit.limitValue,
        remaining: 0,
        overageAllowed: true,
        overageUnits,
        overageCost,
        message: `Se aplicará cargo por excedente: ${overageUnits} unidades`,
      };
    }

    return {
      allowed: false,
      currentUsage,
      limit: limit.limitValue,
      remaining: Math.max(0, remaining),
      overageAllowed: false,
      message: `Límite alcanzado: ${limit.limitName}`,
    };
  }

  /**
   * Get current usage for a tenant and limit key
   */
  async getCurrentUsage(tenantId: string, limitKey: string): Promise<number> {
    // Get current period
    const now = new Date();
    const periodStart = new Date(now.getFullYear(), now.getMonth(), 1);

    const usage = await this.usageRepository.findOne({
      where: {
        tenantId,
        periodStart,
      },
    });

    if (!usage) {
      return 0;
    }

    // Map limit key to usage field
    const usageMap: Record<string, keyof UsageTracking> = {
      users: 'activeUsers',
      storage_gb: 'storageUsedGb',
      api_calls: 'apiCalls',
      branches: 'activeBranches',
      documents: 'documentsCount',
      invoices: 'invoicesGenerated',
      // Add more mappings as needed
    };

    const field = usageMap[limitKey];
    if (field && usage[field] !== undefined) {
      return Number(usage[field]);
    }

    return 0;
  }

  /**
   * Validate all limits for a tenant
   */
  async validateAllLimits(tenantId: string): Promise<{
    valid: boolean;
    violations: Array<{ limitKey: string; message: string }>;
  }> {
    const limits = await this.getTenantLimits(tenantId);
    const violations: Array<{ limitKey: string; message: string }> = [];

    for (const limit of limits) {
      const currentUsage = await this.getCurrentUsage(tenantId, limit.limitKey);
      const check = await this.checkUsage(tenantId, limit.limitKey, currentUsage);

      if (!check.allowed) {
        violations.push({
          limitKey: limit.limitKey,
          message: check.message,
        });
      }
    }

    return {
      valid: violations.length === 0,
      violations,
    };
  }

  /**
   * Copy limits from one plan to another
   */
  async copyLimitsFromPlan(sourcePlanId: string, targetPlanId: string): Promise<PlanLimit[]> {
    const sourceLimits = await this.findByPlan(sourcePlanId);
    const createdLimits: PlanLimit[] = [];

    for (const sourceLimit of sourceLimits) {
      const limit = await this.create({
        planId: targetPlanId,
        limitKey: sourceLimit.limitKey,
        limitName: sourceLimit.limitName,
        limitValue: sourceLimit.limitValue,
        limitType: sourceLimit.limitType,
        allowOverage: sourceLimit.allowOverage,
        overageUnitPrice: Number(sourceLimit.overageUnitPrice),
        overageCurrency: sourceLimit.overageCurrency,
      });
      createdLimits.push(limit);
    }

    logger.info('Plan limits copied', {
      sourcePlanId,
      targetPlanId,
      count: createdLimits.length,
    });

    return createdLimits;
  }
}
