import { Repository, DataSource, Between, In, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { BaseService } from '../../../shared/services/base.service';
import { ServiceResult, QueryOptions } from '../../../shared/types';
import {
  Promotion,
  PromotionStatus,
  PromotionType,
  DiscountApplication,
} from '../entities/promotion.entity';
import { PromotionProduct, ProductRole } from '../entities/promotion-product.entity';

export interface PromotionProductInput {
  productId: string;
  productCode: string;
  productName: string;
  variantId?: string;
  role?: ProductRole;
  discountPercent?: number;
  discountAmount?: number;
  fixedPrice?: number;
  minQuantity?: number;
  maxQuantity?: number;
  bundleQuantity?: number;
  getQuantity?: number;
}

export interface CreatePromotionInput {
  code: string;
  name: string;
  description?: string;
  type: PromotionType;
  discountApplication?: DiscountApplication;
  discountPercent?: number;
  discountAmount?: number;
  maxDiscount?: number;
  buyQuantity?: number;
  getQuantity?: number;
  getDiscountPercent?: number;
  quantityTiers?: { minQuantity: number; discountPercent: number }[];
  bundlePrice?: number;
  startDate: Date;
  endDate?: Date;
  validDays?: string[];
  validHoursStart?: string;
  validHoursEnd?: string;
  maxUsesTotal?: number;
  maxUsesPerCustomer?: number;
  minOrderAmount?: number;
  minQuantity?: number;
  appliesToAllProducts?: boolean;
  includedCategories?: string[];
  excludedCategories?: string[];
  excludedProducts?: string[];
  includedBranches?: string[];
  customerLevels?: string[];
  newCustomersOnly?: boolean;
  loyaltyMembersOnly?: boolean;
  stackable?: boolean;
  stackableWith?: string[];
  priority?: number;
  displayName?: string;
  badgeText?: string;
  badgeColor?: string;
  imageUrl?: string;
  posEnabled?: boolean;
  ecommerceEnabled?: boolean;
  termsAndConditions?: string;
  products?: PromotionProductInput[];
}

export interface PromotionQueryOptions extends QueryOptions {
  status?: PromotionStatus;
  type?: PromotionType;
  branchId?: string;
  startDate?: Date;
  endDate?: Date;
  activeOnly?: boolean;
}

export class PromotionService extends BaseService<Promotion> {
  constructor(
    repository: Repository<Promotion>,
    private readonly productRepository: Repository<PromotionProduct>,
    private readonly dataSource: DataSource
  ) {
    super(repository);
  }

  /**
   * Create a new promotion with products
   */
  async createPromotion(
    tenantId: string,
    input: CreatePromotionInput,
    userId: string
  ): Promise<ServiceResult<Promotion>> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // Check for duplicate code
      const existing = await queryRunner.manager.findOne(Promotion, {
        where: { tenantId, code: input.code.toUpperCase() },
      });

      if (existing) {
        return {
          success: false,
          error: { code: 'DUPLICATE_CODE', message: 'Promotion code already exists' },
        };
      }

      const promotion = queryRunner.manager.create(Promotion, {
        tenantId,
        code: input.code.toUpperCase(),
        name: input.name,
        description: input.description,
        type: input.type,
        status: PromotionStatus.DRAFT,
        discountApplication: input.discountApplication ?? DiscountApplication.LINE,
        discountPercent: input.discountPercent,
        discountAmount: input.discountAmount,
        maxDiscount: input.maxDiscount,
        buyQuantity: input.buyQuantity,
        getQuantity: input.getQuantity,
        getDiscountPercent: input.getDiscountPercent,
        quantityTiers: input.quantityTiers,
        bundlePrice: input.bundlePrice,
        startDate: input.startDate,
        endDate: input.endDate,
        validDays: input.validDays,
        validHoursStart: input.validHoursStart,
        validHoursEnd: input.validHoursEnd,
        maxUsesTotal: input.maxUsesTotal,
        maxUsesPerCustomer: input.maxUsesPerCustomer,
        minOrderAmount: input.minOrderAmount,
        minQuantity: input.minQuantity,
        appliesToAllProducts: input.appliesToAllProducts ?? false,
        includedCategories: input.includedCategories,
        excludedCategories: input.excludedCategories,
        excludedProducts: input.excludedProducts,
        includedBranches: input.includedBranches,
        customerLevels: input.customerLevels,
        newCustomersOnly: input.newCustomersOnly ?? false,
        loyaltyMembersOnly: input.loyaltyMembersOnly ?? false,
        stackable: input.stackable ?? false,
        stackableWith: input.stackableWith,
        priority: input.priority ?? 0,
        displayName: input.displayName,
        badgeText: input.badgeText,
        badgeColor: input.badgeColor ?? '#ff0000',
        imageUrl: input.imageUrl,
        posEnabled: input.posEnabled ?? true,
        ecommerceEnabled: input.ecommerceEnabled ?? true,
        termsAndConditions: input.termsAndConditions,
        createdBy: userId,
      });

      const savedPromotion = await queryRunner.manager.save(promotion);

      // Create promotion products
      if (input.products && input.products.length > 0) {
        for (const productInput of input.products) {
          const promotionProduct = queryRunner.manager.create(PromotionProduct, {
            tenantId,
            promotionId: savedPromotion.id,
            productId: productInput.productId,
            productCode: productInput.productCode,
            productName: productInput.productName,
            variantId: productInput.variantId,
            role: productInput.role ?? ProductRole.BOTH,
            discountPercent: productInput.discountPercent,
            discountAmount: productInput.discountAmount,
            fixedPrice: productInput.fixedPrice,
            minQuantity: productInput.minQuantity ?? 1,
            maxQuantity: productInput.maxQuantity,
            bundleQuantity: productInput.bundleQuantity ?? 1,
            getQuantity: productInput.getQuantity,
          });
          await queryRunner.manager.save(promotionProduct);
        }
      }

      await queryRunner.commitTransaction();

      // Reload with products
      const result = await this.repository.findOne({
        where: { id: savedPromotion.id, tenantId },
        relations: ['products'],
      });

      return { success: true, data: result! };
    } catch (error) {
      await queryRunner.rollbackTransaction();
      return {
        success: false,
        error: {
          code: 'CREATE_PROMOTION_ERROR',
          message: error instanceof Error ? error.message : 'Failed to create promotion',
        },
      };
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * Update promotion
   */
  async updatePromotion(
    tenantId: string,
    id: string,
    input: Partial<CreatePromotionInput>,
    userId: string
  ): Promise<ServiceResult<Promotion>> {
    const promotion = await this.repository.findOne({
      where: { id, tenantId },
    });

    if (!promotion) {
      return {
        success: false,
        error: { code: 'NOT_FOUND', message: 'Promotion not found' },
      };
    }

    // Don't allow editing active promotions (only some fields)
    if (promotion.status === PromotionStatus.ACTIVE) {
      const allowedFields = ['endDate', 'maxUsesTotal', 'priority', 'status'];
      const inputKeys = Object.keys(input);
      const hasDisallowedFields = inputKeys.some(k => !allowedFields.includes(k));

      if (hasDisallowedFields) {
        return {
          success: false,
          error: {
            code: 'PROMOTION_ACTIVE',
            message: 'Cannot modify active promotion. Only endDate, maxUsesTotal, priority, and status can be changed.',
          },
        };
      }
    }

    // Update allowed fields
    const { products, ...updateData } = input;
    Object.assign(promotion, updateData, { updatedBy: userId });

    const saved = await this.repository.save(promotion);
    return { success: true, data: saved };
  }

  /**
   * Activate promotion
   */
  async activatePromotion(
    tenantId: string,
    id: string,
    userId: string
  ): Promise<ServiceResult<Promotion>> {
    const promotion = await this.repository.findOne({
      where: { id, tenantId },
      relations: ['products'],
    });

    if (!promotion) {
      return {
        success: false,
        error: { code: 'NOT_FOUND', message: 'Promotion not found' },
      };
    }

    if (promotion.status !== PromotionStatus.DRAFT && promotion.status !== PromotionStatus.SCHEDULED) {
      return {
        success: false,
        error: { code: 'INVALID_STATUS', message: 'Can only activate draft or scheduled promotions' },
      };
    }

    // Validate promotion has required data
    if (!promotion.appliesToAllProducts && (!promotion.products || promotion.products.length === 0)) {
      if (!promotion.includedCategories || promotion.includedCategories.length === 0) {
        return {
          success: false,
          error: { code: 'NO_PRODUCTS', message: 'Promotion must have products or categories defined' },
        };
      }
    }

    const now = new Date();
    if (promotion.startDate > now) {
      promotion.status = PromotionStatus.SCHEDULED;
    } else {
      promotion.status = PromotionStatus.ACTIVE;
    }

    promotion.updatedBy = userId;
    const saved = await this.repository.save(promotion);

    return { success: true, data: saved };
  }

  /**
   * Pause promotion
   */
  async pausePromotion(
    tenantId: string,
    id: string,
    userId: string
  ): Promise<ServiceResult<Promotion>> {
    const promotion = await this.repository.findOne({
      where: { id, tenantId },
    });

    if (!promotion) {
      return {
        success: false,
        error: { code: 'NOT_FOUND', message: 'Promotion not found' },
      };
    }

    if (promotion.status !== PromotionStatus.ACTIVE) {
      return {
        success: false,
        error: { code: 'INVALID_STATUS', message: 'Can only pause active promotions' },
      };
    }

    promotion.status = PromotionStatus.PAUSED;
    promotion.updatedBy = userId;

    const saved = await this.repository.save(promotion);
    return { success: true, data: saved };
  }

  /**
   * End promotion
   */
  async endPromotion(
    tenantId: string,
    id: string,
    userId: string
  ): Promise<ServiceResult<Promotion>> {
    const promotion = await this.repository.findOne({
      where: { id, tenantId },
    });

    if (!promotion) {
      return {
        success: false,
        error: { code: 'NOT_FOUND', message: 'Promotion not found' },
      };
    }

    if ([PromotionStatus.ENDED, PromotionStatus.CANCELLED].includes(promotion.status)) {
      return {
        success: false,
        error: { code: 'INVALID_STATUS', message: 'Promotion is already ended or cancelled' },
      };
    }

    promotion.status = PromotionStatus.ENDED;
    promotion.endDate = new Date();
    promotion.updatedBy = userId;

    const saved = await this.repository.save(promotion);
    return { success: true, data: saved };
  }

  /**
   * Cancel promotion
   */
  async cancelPromotion(
    tenantId: string,
    id: string,
    userId: string
  ): Promise<ServiceResult<Promotion>> {
    const promotion = await this.repository.findOne({
      where: { id, tenantId },
    });

    if (!promotion) {
      return {
        success: false,
        error: { code: 'NOT_FOUND', message: 'Promotion not found' },
      };
    }

    if (promotion.status === PromotionStatus.CANCELLED) {
      return {
        success: false,
        error: { code: 'ALREADY_CANCELLED', message: 'Promotion is already cancelled' },
      };
    }

    promotion.status = PromotionStatus.CANCELLED;
    promotion.updatedBy = userId;

    const saved = await this.repository.save(promotion);
    return { success: true, data: saved };
  }

  /**
   * Add products to promotion
   */
  async addProducts(
    tenantId: string,
    promotionId: string,
    products: PromotionProductInput[],
    userId: string
  ): Promise<ServiceResult<PromotionProduct[]>> {
    const promotion = await this.repository.findOne({
      where: { id: promotionId, tenantId },
    });

    if (!promotion) {
      return {
        success: false,
        error: { code: 'NOT_FOUND', message: 'Promotion not found' },
      };
    }

    if (promotion.status === PromotionStatus.ACTIVE) {
      return {
        success: false,
        error: { code: 'PROMOTION_ACTIVE', message: 'Cannot add products to active promotion' },
      };
    }

    const created: PromotionProduct[] = [];

    for (const productInput of products) {
      // Check if already exists
      const existing = await this.productRepository.findOne({
        where: {
          tenantId,
          promotionId,
          productId: productInput.productId,
          variantId: productInput.variantId ?? undefined,
        },
      });

      if (existing) continue;

      const promotionProduct = this.productRepository.create({
        tenantId,
        promotionId,
        productId: productInput.productId,
        productCode: productInput.productCode,
        productName: productInput.productName,
        variantId: productInput.variantId,
        role: productInput.role ?? ProductRole.BOTH,
        discountPercent: productInput.discountPercent,
        discountAmount: productInput.discountAmount,
        fixedPrice: productInput.fixedPrice,
        minQuantity: productInput.minQuantity ?? 1,
        maxQuantity: productInput.maxQuantity,
        bundleQuantity: productInput.bundleQuantity ?? 1,
        getQuantity: productInput.getQuantity,
      });

      const saved = await this.productRepository.save(promotionProduct);
      created.push(saved);
    }

    return { success: true, data: created };
  }

  /**
   * Remove product from promotion
   */
  async removeProduct(
    tenantId: string,
    promotionId: string,
    productId: string
  ): Promise<ServiceResult<boolean>> {
    const promotion = await this.repository.findOne({
      where: { id: promotionId, tenantId },
    });

    if (!promotion) {
      return {
        success: false,
        error: { code: 'NOT_FOUND', message: 'Promotion not found' },
      };
    }

    if (promotion.status === PromotionStatus.ACTIVE) {
      return {
        success: false,
        error: { code: 'PROMOTION_ACTIVE', message: 'Cannot remove products from active promotion' },
      };
    }

    await this.productRepository.delete({
      tenantId,
      promotionId,
      productId,
    });

    return { success: true, data: true };
  }

  /**
   * Find promotions with filters
   */
  async findPromotions(
    tenantId: string,
    options: PromotionQueryOptions
  ): Promise<{ data: Promotion[]; total: number }> {
    const qb = this.repository.createQueryBuilder('promotion')
      .leftJoinAndSelect('promotion.products', 'products')
      .where('promotion.tenantId = :tenantId', { tenantId });

    if (options.status) {
      qb.andWhere('promotion.status = :status', { status: options.status });
    }

    if (options.type) {
      qb.andWhere('promotion.type = :type', { type: options.type });
    }

    if (options.branchId) {
      qb.andWhere('(promotion.includedBranches IS NULL OR promotion.includedBranches @> :branchArray)', {
        branchArray: JSON.stringify([options.branchId]),
      });
    }

    if (options.activeOnly) {
      const now = new Date();
      qb.andWhere('promotion.status = :activeStatus', { activeStatus: PromotionStatus.ACTIVE })
        .andWhere('promotion.startDate <= :now', { now })
        .andWhere('(promotion.endDate IS NULL OR promotion.endDate >= :now)', { now });
    }

    if (options.startDate && options.endDate) {
      qb.andWhere('promotion.startDate BETWEEN :startDate AND :endDate', {
        startDate: options.startDate,
        endDate: options.endDate,
      });
    }

    const page = options.page ?? 1;
    const limit = options.limit ?? 20;
    qb.skip((page - 1) * limit).take(limit);
    qb.orderBy('promotion.priority', 'DESC')
      .addOrderBy('promotion.createdAt', 'DESC');

    const [data, total] = await qb.getManyAndCount();
    return { data, total };
  }

  /**
   * Get promotion with products
   */
  async getPromotionWithProducts(
    tenantId: string,
    id: string
  ): Promise<Promotion | null> {
    return this.repository.findOne({
      where: { id, tenantId },
      relations: ['products'],
    });
  }

  /**
   * Increment usage count
   */
  async incrementUsage(
    tenantId: string,
    id: string
  ): Promise<void> {
    await this.repository.increment(
      { id, tenantId },
      'currentUses',
      1
    );
  }

  /**
   * Get promotion statistics
   */
  async getPromotionStats(
    tenantId: string,
    id: string
  ): Promise<{
    totalUses: number;
    totalDiscount: number;
    averageDiscount: number;
  }> {
    const promotion = await this.repository.findOne({
      where: { id, tenantId },
    });

    if (!promotion) {
      return { totalUses: 0, totalDiscount: 0, averageDiscount: 0 };
    }

    // Would need to query order data for actual discount amounts
    // For now, return basic usage stats
    return {
      totalUses: promotion.currentUses,
      totalDiscount: 0, // Would calculate from orders
      averageDiscount: 0,
    };
  }
}
