import { Repository, IsNull, Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import {
  DiscountRule,
  DiscountType,
  DiscountAppliesTo,
  DiscountCondition,
} from '../entities';

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface CreateDiscountRuleDto {
  code: string;
  name: string;
  description?: string;
  companyId?: string;
  discountType: DiscountType;
  discountValue: number;
  maxDiscountAmount?: number;
  appliesTo: DiscountAppliesTo;
  appliesToId?: string;
  conditionType?: DiscountCondition;
  conditionValue?: number;
  startDate?: Date;
  endDate?: Date;
  priority?: number;
  combinable?: boolean;
  usageLimit?: number;
  isActive?: boolean;
}

export interface UpdateDiscountRuleDto {
  code?: string;
  name?: string;
  description?: string;
  companyId?: string;
  discountType?: DiscountType;
  discountValue?: number;
  maxDiscountAmount?: number;
  appliesTo?: DiscountAppliesTo;
  appliesToId?: string;
  conditionType?: DiscountCondition;
  conditionValue?: number;
  startDate?: Date;
  endDate?: Date;
  priority?: number;
  combinable?: boolean;
  usageLimit?: number;
  isActive?: boolean;
}

export interface DiscountRuleFilters {
  code?: string;
  discountType?: DiscountType;
  appliesTo?: DiscountAppliesTo;
  companyId?: string;
  isActive?: boolean;
  activeOnDate?: Date;
  search?: string;
}

export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface DiscountCalculation {
  originalAmount: number;
  discountAmount: number;
  finalAmount: number;
  discountPercentage: number;
  appliedRule: DiscountRule;
}

export interface ApplicabilityContext {
  targetType: DiscountAppliesTo;
  targetId?: string;
  quantity?: number;
  amount?: number;
  customerId?: string;
  customerGroupId?: string;
  isFirstPurchase?: boolean;
  date?: Date;
}

export class DiscountRuleService {
  constructor(private readonly discountRuleRepository: Repository<DiscountRule>) {}

  async create(ctx: ServiceContext, data: CreateDiscountRuleDto): Promise<DiscountRule> {
    const existing = await this.discountRuleRepository.findOne({
      where: { tenantId: ctx.tenantId, code: data.code, deletedAt: IsNull() },
    });

    if (existing) {
      throw new Error(`Discount rule with code '${data.code}' already exists`);
    }

    if (data.discountValue <= 0) {
      throw new Error('Discount value must be positive');
    }

    if (data.discountType === DiscountType.PERCENTAGE && data.discountValue > 100) {
      throw new Error('Percentage discount cannot exceed 100%');
    }

    const discountRule = this.discountRuleRepository.create({
      tenantId: ctx.tenantId,
      code: data.code,
      name: data.name,
      description: data.description,
      companyId: data.companyId,
      discountType: data.discountType,
      discountValue: data.discountValue,
      maxDiscountAmount: data.maxDiscountAmount,
      appliesTo: data.appliesTo,
      appliesToId: data.appliesToId,
      conditionType: data.conditionType ?? DiscountCondition.NONE,
      conditionValue: data.conditionValue,
      startDate: data.startDate,
      endDate: data.endDate,
      priority: data.priority ?? 10,
      combinable: data.combinable ?? true,
      usageLimit: data.usageLimit,
      usageCount: 0,
      isActive: data.isActive ?? true,
      createdBy: ctx.userId ?? null,
    });

    return this.discountRuleRepository.save(discountRule);
  }

  async findById(ctx: ServiceContext, id: string): Promise<DiscountRule | null> {
    return this.discountRuleRepository.findOne({
      where: { id, tenantId: ctx.tenantId, deletedAt: IsNull() },
    });
  }

  async findByCode(ctx: ServiceContext, code: string): Promise<DiscountRule | null> {
    return this.discountRuleRepository.findOne({
      where: { tenantId: ctx.tenantId, code, deletedAt: IsNull() },
    });
  }

  async findAll(ctx: ServiceContext): Promise<DiscountRule[]> {
    return this.discountRuleRepository.find({
      where: { tenantId: ctx.tenantId, deletedAt: IsNull() },
      order: { priority: 'ASC', name: 'ASC' },
    });
  }

  async findActive(ctx: ServiceContext, date?: Date): Promise<DiscountRule[]> {
    const targetDate = date || new Date();

    const qb = this.discountRuleRepository
      .createQueryBuilder('dr')
      .where('dr.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('dr.deleted_at IS NULL')
      .andWhere('dr.is_active = true')
      .andWhere('(dr.start_date IS NULL OR dr.start_date <= :date)', { date: targetDate })
      .andWhere('(dr.end_date IS NULL OR dr.end_date >= :date)', { date: targetDate })
      .andWhere('(dr.usage_limit IS NULL OR dr.usage_count < dr.usage_limit)')
      .orderBy('dr.priority', 'ASC');

    return qb.getMany();
  }

  async findWithFilters(
    ctx: ServiceContext,
    filters: DiscountRuleFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<DiscountRule>> {
    const qb = this.discountRuleRepository
      .createQueryBuilder('dr')
      .where('dr.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('dr.deleted_at IS NULL');

    if (filters.code) {
      qb.andWhere('dr.code = :code', { code: filters.code });
    }

    if (filters.discountType) {
      qb.andWhere('dr.discount_type = :discountType', { discountType: filters.discountType });
    }

    if (filters.appliesTo) {
      qb.andWhere('dr.applies_to = :appliesTo', { appliesTo: filters.appliesTo });
    }

    if (filters.companyId) {
      qb.andWhere('dr.company_id = :companyId', { companyId: filters.companyId });
    }

    if (filters.isActive !== undefined) {
      qb.andWhere('dr.is_active = :isActive', { isActive: filters.isActive });
    }

    if (filters.activeOnDate) {
      qb.andWhere('(dr.start_date IS NULL OR dr.start_date <= :date)', { date: filters.activeOnDate });
      qb.andWhere('(dr.end_date IS NULL OR dr.end_date >= :date)', { date: filters.activeOnDate });
    }

    if (filters.search) {
      qb.andWhere('(dr.name ILIKE :search OR dr.code ILIKE :search OR dr.description ILIKE :search)', {
        search: `%${filters.search}%`,
      });
    }

    const [data, total] = await qb
      .orderBy('dr.priority', 'ASC')
      .addOrderBy('dr.name', 'ASC')
      .skip((page - 1) * limit)
      .take(limit)
      .getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  async update(ctx: ServiceContext, id: string, data: UpdateDiscountRuleDto): Promise<DiscountRule | null> {
    const discountRule = await this.findById(ctx, id);
    if (!discountRule) return null;

    if (data.code && data.code !== discountRule.code) {
      const existing = await this.discountRuleRepository.findOne({
        where: { tenantId: ctx.tenantId, code: data.code, deletedAt: IsNull() },
      });
      if (existing) {
        throw new Error(`Discount rule with code '${data.code}' already exists`);
      }
    }

    if (data.discountValue !== undefined && data.discountValue <= 0) {
      throw new Error('Discount value must be positive');
    }

    const discountType = data.discountType ?? discountRule.discountType;
    const discountValue = data.discountValue ?? discountRule.discountValue;

    if (discountType === DiscountType.PERCENTAGE && discountValue > 100) {
      throw new Error('Percentage discount cannot exceed 100%');
    }

    Object.assign(discountRule, {
      ...data,
      updatedBy: ctx.userId ?? null,
    });

    return this.discountRuleRepository.save(discountRule);
  }

  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    const discountRule = await this.findById(ctx, id);
    if (!discountRule) return false;

    discountRule.deletedAt = new Date();
    discountRule.deletedBy = ctx.userId ?? null;
    await this.discountRuleRepository.save(discountRule);

    return true;
  }

  async activate(ctx: ServiceContext, id: string): Promise<DiscountRule | null> {
    return this.update(ctx, id, { isActive: true });
  }

  async deactivate(ctx: ServiceContext, id: string): Promise<DiscountRule | null> {
    return this.update(ctx, id, { isActive: false });
  }

  // Business Logic Methods

  async isApplicable(ctx: ServiceContext, ruleId: string, context: ApplicabilityContext): Promise<boolean> {
    const rule = await this.findById(ctx, ruleId);
    if (!rule) return false;

    return this.checkApplicability(rule, context);
  }

  async getApplicableRules(ctx: ServiceContext, context: ApplicabilityContext): Promise<DiscountRule[]> {
    const activeRules = await this.findActive(ctx, context.date);

    const applicableRules = activeRules.filter(rule => this.checkApplicability(rule, context));

    return applicableRules.sort((a, b) => a.priority - b.priority);
  }

  async calculateDiscount(
    ctx: ServiceContext,
    ruleId: string,
    baseAmount: number,
    quantity = 1,
  ): Promise<DiscountCalculation | null> {
    const rule = await this.findById(ctx, ruleId);
    if (!rule || !rule.isActive) return null;

    let discountAmount = 0;

    switch (rule.discountType) {
      case DiscountType.PERCENTAGE:
        discountAmount = (baseAmount * rule.discountValue) / 100;
        break;
      case DiscountType.FIXED:
        discountAmount = rule.discountValue * quantity;
        break;
      case DiscountType.PRICE_OVERRIDE:
        discountAmount = Math.max(0, baseAmount - rule.discountValue * quantity);
        break;
    }

    // Apply max discount cap if set
    if (rule.maxDiscountAmount && discountAmount > rule.maxDiscountAmount) {
      discountAmount = rule.maxDiscountAmount;
    }

    // Ensure discount doesn't exceed base amount
    discountAmount = Math.min(discountAmount, baseAmount);

    const finalAmount = baseAmount - discountAmount;
    const discountPercentage = baseAmount > 0 ? (discountAmount / baseAmount) * 100 : 0;

    return {
      originalAmount: baseAmount,
      discountAmount,
      finalAmount,
      discountPercentage,
      appliedRule: rule,
    };
  }

  async calculateBestDiscount(
    ctx: ServiceContext,
    context: ApplicabilityContext,
    baseAmount: number,
    quantity = 1,
  ): Promise<DiscountCalculation | null> {
    const applicableRules = await this.getApplicableRules(ctx, context);

    if (applicableRules.length === 0) return null;

    // Calculate discount for each rule and find the best one
    let bestDiscount: DiscountCalculation | null = null;

    for (const rule of applicableRules) {
      const calculation = await this.calculateDiscount(ctx, rule.id, baseAmount, quantity);
      if (calculation) {
        if (!bestDiscount || calculation.discountAmount > bestDiscount.discountAmount) {
          bestDiscount = calculation;
        }
      }
    }

    return bestDiscount;
  }

  async calculateCombinedDiscounts(
    ctx: ServiceContext,
    context: ApplicabilityContext,
    baseAmount: number,
    quantity = 1,
  ): Promise<{
    totalDiscount: number;
    finalAmount: number;
    appliedRules: DiscountCalculation[];
  }> {
    const applicableRules = await this.getApplicableRules(ctx, context);
    const combinableRules = applicableRules.filter(r => r.combinable);

    const appliedRules: DiscountCalculation[] = [];
    let currentAmount = baseAmount;
    let totalDiscount = 0;

    for (const rule of combinableRules) {
      const calculation = await this.calculateDiscount(ctx, rule.id, currentAmount, quantity);
      if (calculation && calculation.discountAmount > 0) {
        appliedRules.push(calculation);
        totalDiscount += calculation.discountAmount;
        currentAmount = calculation.finalAmount;
      }
    }

    return {
      totalDiscount,
      finalAmount: currentAmount,
      appliedRules,
    };
  }

  async incrementUsageCount(ctx: ServiceContext, id: string): Promise<DiscountRule | null> {
    const rule = await this.findById(ctx, id);
    if (!rule) return null;

    if (rule.usageLimit && rule.usageCount >= rule.usageLimit) {
      throw new Error('Discount rule usage limit reached');
    }

    rule.usageCount = (rule.usageCount || 0) + 1;
    return this.discountRuleRepository.save(rule);
  }

  async resetUsageCount(ctx: ServiceContext, id: string): Promise<DiscountRule | null> {
    const rule = await this.findById(ctx, id);
    if (!rule) return null;

    rule.usageCount = 0;
    rule.updatedBy = ctx.userId ?? null;
    return this.discountRuleRepository.save(rule);
  }

  async getRemainingUsage(ctx: ServiceContext, id: string): Promise<number | null> {
    const rule = await this.findById(ctx, id);
    if (!rule) return null;
    if (!rule.usageLimit) return null; // Unlimited

    return Math.max(0, rule.usageLimit - (rule.usageCount || 0));
  }

  private checkApplicability(rule: DiscountRule, context: ApplicabilityContext): boolean {
    // Check if rule is active
    if (!rule.isActive) return false;

    // Check date range
    const checkDate = context.date || new Date();
    if (rule.startDate && rule.startDate > checkDate) return false;
    if (rule.endDate && rule.endDate < checkDate) return false;

    // Check usage limit
    if (rule.usageLimit && rule.usageCount >= rule.usageLimit) return false;

    // Check applies to
    if (rule.appliesTo !== DiscountAppliesTo.ALL) {
      if (rule.appliesTo !== context.targetType) return false;
      if (rule.appliesToId && rule.appliesToId !== context.targetId) return false;
    }

    // Check conditions
    if (rule.conditionType && rule.conditionType !== DiscountCondition.NONE) {
      if (!this.checkCondition(rule, context)) return false;
    }

    return true;
  }

  private checkCondition(rule: DiscountRule, context: ApplicabilityContext): boolean {
    switch (rule.conditionType) {
      case DiscountCondition.MIN_QUANTITY:
        return (context.quantity || 0) >= (rule.conditionValue || 0);

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

      case DiscountCondition.DATE_RANGE:
        // Already handled by startDate/endDate
        return true;

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

      case DiscountCondition.NONE:
      default:
        return true;
    }
  }
}
