import { Repository, LessThanOrEqual, MoreThanOrEqual, IsNull, Or } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import {
  DiscountRule,
  DiscountType,
  DiscountAppliesTo,
  DiscountCondition,
} from './entities/discount-rule.entity.js';
import { NotFoundError, ValidationError, ConflictError } from '../../shared/errors/index.js';
import { logger } from '../../shared/utils/logger.js';

// ============================================================================
// TYPES
// ============================================================================

export interface CreateDiscountRuleDto {
  code: string;
  name: string;
  description?: string;
  discount_type?: DiscountType | 'percentage' | 'fixed' | 'price_override';
  discountType?: DiscountType | 'percentage' | 'fixed' | 'price_override';
  discount_value: number;
  discountValue?: number;
  max_discount_amount?: number | null;
  maxDiscountAmount?: number | null;
  applies_to?: DiscountAppliesTo | 'all' | 'category' | 'product' | 'customer' | 'customer_group';
  appliesTo?: DiscountAppliesTo | 'all' | 'category' | 'product' | 'customer' | 'customer_group';
  applies_to_id?: string | null;
  appliesToId?: string | null;
  condition_type?: DiscountCondition | 'none' | 'min_quantity' | 'min_amount' | 'date_range' | 'first_purchase';
  conditionType?: DiscountCondition | 'none' | 'min_quantity' | 'min_amount' | 'date_range' | 'first_purchase';
  condition_value?: number | null;
  conditionValue?: number | null;
  start_date?: Date | string | null;
  startDate?: Date | string | null;
  end_date?: Date | string | null;
  endDate?: Date | string | null;
  priority?: number;
  combinable?: boolean;
  usage_limit?: number | null;
  usageLimit?: number | null;
}

export interface UpdateDiscountRuleDto {
  name?: string;
  description?: string | null;
  discount_type?: DiscountType | 'percentage' | 'fixed' | 'price_override';
  discountType?: DiscountType | 'percentage' | 'fixed' | 'price_override';
  discount_value?: number;
  discountValue?: number;
  max_discount_amount?: number | null;
  maxDiscountAmount?: number | null;
  applies_to?: DiscountAppliesTo | 'all' | 'category' | 'product' | 'customer' | 'customer_group';
  appliesTo?: DiscountAppliesTo | 'all' | 'category' | 'product' | 'customer' | 'customer_group';
  applies_to_id?: string | null;
  appliesToId?: string | null;
  condition_type?: DiscountCondition | 'none' | 'min_quantity' | 'min_amount' | 'date_range' | 'first_purchase';
  conditionType?: DiscountCondition | 'none' | 'min_quantity' | 'min_amount' | 'date_range' | 'first_purchase';
  condition_value?: number | null;
  conditionValue?: number | null;
  start_date?: Date | string | null;
  startDate?: Date | string | null;
  end_date?: Date | string | null;
  endDate?: Date | string | null;
  priority?: number;
  combinable?: boolean;
  usage_limit?: number | null;
  usageLimit?: number | null;
  is_active?: boolean;
  isActive?: boolean;
}

export interface ApplyDiscountContext {
  productId?: string;
  categoryId?: string;
  customerId?: string;
  customerGroupId?: string;
  quantity: number;
  unitPrice: number;
  totalAmount: number;
  isFirstPurchase?: boolean;
}

export interface DiscountResult {
  ruleId: string;
  ruleCode: string;
  ruleName: string;
  discountType: DiscountType;
  discountAmount: number;
  discountPercent: number;
  originalAmount: number;
  finalAmount: number;
}

export interface ApplyDiscountsResult {
  appliedDiscounts: DiscountResult[];
  totalDiscount: number;
  originalAmount: number;
  finalAmount: number;
}

// ============================================================================
// SERVICE
// ============================================================================

class DiscountRulesService {
  private repository: Repository<DiscountRule>;

  constructor() {
    this.repository = AppDataSource.getRepository(DiscountRule);
  }

  /**
   * Apply applicable discount rules to a context
   */
  async applyDiscounts(
    tenantId: string,
    context: ApplyDiscountContext
  ): Promise<ApplyDiscountsResult> {
    logger.debug('Applying discounts', { tenantId, context });

    const applicableRules = await this.findApplicableRules(tenantId, context);
    const appliedDiscounts: DiscountResult[] = [];
    let runningAmount = context.totalAmount;
    let totalDiscount = 0;

    // Sort by priority (lower = higher priority)
    const sortedRules = applicableRules.sort((a, b) => a.priority - b.priority);

    for (const rule of sortedRules) {
      // Check if rule can be combined with already applied discounts
      if (appliedDiscounts.length > 0 && !rule.combinable) {
        logger.debug('Skipping non-combinable rule', { ruleCode: rule.code });
        continue;
      }

      // Check if previous discounts are non-combinable
      const hasNonCombinable = appliedDiscounts.some(
        (d) => !sortedRules.find((r) => r.id === d.ruleId)?.combinable
      );
      if (hasNonCombinable && !rule.combinable) {
        continue;
      }

      // Check usage limit
      if (rule.usageLimit && rule.usageCount >= rule.usageLimit) {
        logger.debug('Rule usage limit reached', { ruleCode: rule.code });
        continue;
      }

      // Calculate discount
      const discountResult = this.calculateDiscount(rule, runningAmount, context);

      if (discountResult.discountAmount > 0) {
        appliedDiscounts.push(discountResult);
        totalDiscount += discountResult.discountAmount;
        runningAmount = discountResult.finalAmount;

        // Increment usage count
        await this.incrementUsageCount(rule.id);
      }
    }

    return {
      appliedDiscounts,
      totalDiscount,
      originalAmount: context.totalAmount,
      finalAmount: context.totalAmount - totalDiscount,
    };
  }

  /**
   * Calculate discount for a single rule
   */
  private calculateDiscount(
    rule: DiscountRule,
    amount: number,
    context: ApplyDiscountContext
  ): DiscountResult {
    let discountAmount = 0;
    let discountPercent = 0;

    switch (rule.discountType) {
      case DiscountType.PERCENTAGE:
        discountPercent = Number(rule.discountValue);
        discountAmount = (amount * discountPercent) / 100;
        break;

      case DiscountType.FIXED:
        discountAmount = Math.min(Number(rule.discountValue), amount);
        discountPercent = (discountAmount / amount) * 100;
        break;

      case DiscountType.PRICE_OVERRIDE:
        const newPrice = Number(rule.discountValue);
        const totalNewAmount = newPrice * context.quantity;
        discountAmount = Math.max(0, amount - totalNewAmount);
        discountPercent = (discountAmount / amount) * 100;
        break;
    }

    // Apply max discount cap
    if (rule.maxDiscountAmount && discountAmount > Number(rule.maxDiscountAmount)) {
      discountAmount = Number(rule.maxDiscountAmount);
      discountPercent = (discountAmount / amount) * 100;
    }

    return {
      ruleId: rule.id,
      ruleCode: rule.code,
      ruleName: rule.name,
      discountType: rule.discountType,
      discountAmount: Math.round(discountAmount * 100) / 100,
      discountPercent: Math.round(discountPercent * 100) / 100,
      originalAmount: amount,
      finalAmount: Math.round((amount - discountAmount) * 100) / 100,
    };
  }

  /**
   * Find all applicable rules for a context
   */
  private async findApplicableRules(
    tenantId: string,
    context: ApplyDiscountContext
  ): Promise<DiscountRule[]> {
    const now = new Date();

    const queryBuilder = this.repository
      .createQueryBuilder('dr')
      .where('dr.tenant_id = :tenantId', { tenantId })
      .andWhere('dr.is_active = :isActive', { isActive: true })
      .andWhere('(dr.start_date IS NULL OR dr.start_date <= :now)', { now })
      .andWhere('(dr.end_date IS NULL OR dr.end_date >= :now)', { now });

    const allRules = await queryBuilder.getMany();

    // Filter by applies_to and condition
    return allRules.filter((rule) => {
      // Check applies_to
      if (!this.checkAppliesTo(rule, context)) {
        return false;
      }

      // Check condition
      if (!this.checkCondition(rule, context)) {
        return false;
      }

      return true;
    });
  }

  /**
   * Check if rule applies to the context
   */
  private checkAppliesTo(rule: DiscountRule, context: ApplyDiscountContext): boolean {
    switch (rule.appliesTo) {
      case DiscountAppliesTo.ALL:
        return true;

      case DiscountAppliesTo.PRODUCT:
        return rule.appliesToId === context.productId;

      case DiscountAppliesTo.CATEGORY:
        return rule.appliesToId === context.categoryId;

      case DiscountAppliesTo.CUSTOMER:
        return rule.appliesToId === context.customerId;

      case DiscountAppliesTo.CUSTOMER_GROUP:
        return rule.appliesToId === context.customerGroupId;

      default:
        return false;
    }
  }

  /**
   * Check if rule condition is met
   */
  private checkCondition(rule: DiscountRule, context: ApplyDiscountContext): boolean {
    switch (rule.conditionType) {
      case DiscountCondition.NONE:
        return true;

      case DiscountCondition.MIN_QUANTITY:
        return context.quantity >= Number(rule.conditionValue || 0);

      case DiscountCondition.MIN_AMOUNT:
        return context.totalAmount >= Number(rule.conditionValue || 0);

      case DiscountCondition.DATE_RANGE:
        // Already handled in query
        return true;

      case DiscountCondition.FIRST_PURCHASE:
        return context.isFirstPurchase === true;

      default:
        return true;
    }
  }

  /**
   * Increment usage count for a rule
   */
  private async incrementUsageCount(ruleId: string): Promise<void> {
    await this.repository.increment({ id: ruleId }, 'usageCount', 1);
  }

  /**
   * Get all discount rules for a tenant
   */
  async findAll(tenantId: string, activeOnly: boolean = false): Promise<DiscountRule[]> {
    logger.debug('Finding all discount rules', { tenantId, activeOnly });

    const query = this.repository
      .createQueryBuilder('dr')
      .where('dr.tenant_id = :tenantId', { tenantId })
      .orderBy('dr.priority', 'ASC')
      .addOrderBy('dr.name', 'ASC');

    if (activeOnly) {
      query.andWhere('dr.is_active = :isActive', { isActive: true });
    }

    return query.getMany();
  }

  /**
   * Get a specific discount rule by ID
   */
  async findById(id: string, tenantId: string): Promise<DiscountRule> {
    logger.debug('Finding discount rule by id', { id, tenantId });

    const rule = await this.repository.findOne({
      where: { id, tenantId },
    });

    if (!rule) {
      throw new NotFoundError('Regla de descuento no encontrada');
    }

    return rule;
  }

  /**
   * Get a specific discount rule by code
   */
  async findByCode(code: string, tenantId: string): Promise<DiscountRule | null> {
    logger.debug('Finding discount rule by code', { code, tenantId });

    return this.repository.findOne({
      where: { code, tenantId },
    });
  }

  /**
   * Create a new discount rule
   */
  async create(
    dto: CreateDiscountRuleDto,
    tenantId: string,
    userId?: string
  ): Promise<DiscountRule> {
    logger.debug('Creating discount rule', { dto, tenantId });

    // Check for existing
    const existing = await this.findByCode(dto.code, tenantId);
    if (existing) {
      throw new ConflictError(`Ya existe una regla de descuento con código ${dto.code}`);
    }

    // Normalize inputs
    const discountTypeRaw = dto.discount_type ?? dto.discountType ?? 'percentage';
    const discountType = discountTypeRaw as DiscountType;
    const discountValue = dto.discount_value ?? dto.discountValue;
    const maxDiscountAmount = dto.max_discount_amount ?? dto.maxDiscountAmount ?? null;
    const appliesToRaw = dto.applies_to ?? dto.appliesTo ?? 'all';
    const appliesTo = appliesToRaw as DiscountAppliesTo;
    const appliesToId = dto.applies_to_id ?? dto.appliesToId ?? null;
    const conditionTypeRaw = dto.condition_type ?? dto.conditionType ?? 'none';
    const conditionType = conditionTypeRaw as DiscountCondition;
    const conditionValue = dto.condition_value ?? dto.conditionValue ?? null;
    const startDate = dto.start_date ?? dto.startDate ?? null;
    const endDate = dto.end_date ?? dto.endDate ?? null;
    const usageLimit = dto.usage_limit ?? dto.usageLimit ?? null;

    if (discountValue === undefined) {
      throw new ValidationError('discount_value es requerido');
    }

    const rule = this.repository.create({
      tenantId,
      code: dto.code,
      name: dto.name,
      description: dto.description || null,
      discountType,
      discountValue,
      maxDiscountAmount,
      appliesTo,
      appliesToId,
      conditionType,
      conditionValue,
      startDate: startDate ? new Date(startDate) : null,
      endDate: endDate ? new Date(endDate) : null,
      priority: dto.priority ?? 10,
      combinable: dto.combinable ?? true,
      usageLimit,
      createdBy: userId || null,
    });

    const saved = await this.repository.save(rule);

    logger.info('Discount rule created', { id: saved.id, code: dto.code, tenantId });

    return saved;
  }

  /**
   * Update a discount rule
   */
  async update(
    id: string,
    dto: UpdateDiscountRuleDto,
    tenantId: string,
    userId?: string
  ): Promise<DiscountRule> {
    logger.debug('Updating discount rule', { id, dto, tenantId });

    const existing = await this.findById(id, tenantId);

    // Normalize inputs
    const discountTypeRaw = dto.discount_type ?? dto.discountType;
    const discountValue = dto.discount_value ?? dto.discountValue;
    const maxDiscountAmount = dto.max_discount_amount ?? dto.maxDiscountAmount;
    const appliesToRaw = dto.applies_to ?? dto.appliesTo;
    const appliesToId = dto.applies_to_id ?? dto.appliesToId;
    const conditionTypeRaw = dto.condition_type ?? dto.conditionType;
    const conditionValue = dto.condition_value ?? dto.conditionValue;
    const startDate = dto.start_date ?? dto.startDate;
    const endDate = dto.end_date ?? dto.endDate;
    const usageLimit = dto.usage_limit ?? dto.usageLimit;
    const isActive = dto.is_active ?? dto.isActive;

    if (dto.name !== undefined) existing.name = dto.name;
    if (dto.description !== undefined) existing.description = dto.description;
    if (discountTypeRaw !== undefined) existing.discountType = discountTypeRaw as DiscountType;
    if (discountValue !== undefined) existing.discountValue = discountValue;
    if (maxDiscountAmount !== undefined) existing.maxDiscountAmount = maxDiscountAmount;
    if (appliesToRaw !== undefined) existing.appliesTo = appliesToRaw as DiscountAppliesTo;
    if (appliesToId !== undefined) existing.appliesToId = appliesToId;
    if (conditionTypeRaw !== undefined) existing.conditionType = conditionTypeRaw as DiscountCondition;
    if (conditionValue !== undefined) existing.conditionValue = conditionValue;
    if (startDate !== undefined) existing.startDate = startDate ? new Date(startDate) : null;
    if (endDate !== undefined) existing.endDate = endDate ? new Date(endDate) : null;
    if (dto.priority !== undefined) existing.priority = dto.priority;
    if (dto.combinable !== undefined) existing.combinable = dto.combinable;
    if (usageLimit !== undefined) existing.usageLimit = usageLimit;
    if (isActive !== undefined) existing.isActive = isActive;

    existing.updatedBy = userId || null;

    const updated = await this.repository.save(existing);

    logger.info('Discount rule updated', { id, tenantId });

    return updated;
  }

  /**
   * Soft delete a discount rule
   */
  async delete(id: string, tenantId: string, userId?: string): Promise<void> {
    logger.debug('Deleting discount rule', { id, tenantId });

    const existing = await this.findById(id, tenantId);

    existing.deletedAt = new Date();
    existing.deletedBy = userId || null;

    await this.repository.save(existing);

    logger.info('Discount rule deleted', { id, tenantId });
  }

  /**
   * Reset usage count for a rule
   */
  async resetUsageCount(id: string, tenantId: string): Promise<DiscountRule> {
    logger.debug('Resetting usage count', { id, tenantId });

    const rule = await this.findById(id, tenantId);
    rule.usageCount = 0;

    return this.repository.save(rule);
  }

  /**
   * Find rules by product
   */
  async findByProduct(productId: string, tenantId: string): Promise<DiscountRule[]> {
    return this.repository.find({
      where: [
        { tenantId, appliesTo: DiscountAppliesTo.PRODUCT, appliesToId: productId, isActive: true },
        { tenantId, appliesTo: DiscountAppliesTo.ALL, isActive: true },
      ],
      order: { priority: 'ASC' },
    });
  }

  /**
   * Find rules by customer
   */
  async findByCustomer(customerId: string, tenantId: string): Promise<DiscountRule[]> {
    return this.repository.find({
      where: [
        { tenantId, appliesTo: DiscountAppliesTo.CUSTOMER, appliesToId: customerId, isActive: true },
        { tenantId, appliesTo: DiscountAppliesTo.ALL, isActive: true },
      ],
      order: { priority: 'ASC' },
    });
  }
}

export const discountRulesService = new DiscountRulesService();
