import { Repository, DataSource, Between } from 'typeorm';
import { BaseService } from '../../../shared/services/base.service';
import { ServiceResult, QueryOptions } from '../../../shared/types';
import { Coupon, CouponStatus, CouponType, CouponScope } from '../entities/coupon.entity';
import { CouponRedemption, RedemptionStatus } from '../entities/coupon-redemption.entity';

export interface CreateCouponInput {
  code: string;
  name: string;
  description?: string;
  type: CouponType;
  scope?: CouponScope;
  discountPercent?: number;
  discountAmount?: number;
  maxDiscount?: number;
  freeProductId?: string;
  freeProductQuantity?: number;
  validFrom: Date;
  validUntil?: Date;
  maxUses?: number;
  maxUsesPerCustomer?: number;
  minOrderAmount?: number;
  minItems?: number;
  appliesToCategories?: string[];
  appliesToProducts?: string[];
  excludedProducts?: string[];
  includedBranches?: string[];
  customerSpecific?: boolean;
  allowedCustomers?: string[];
  customerLevels?: string[];
  newCustomersOnly?: boolean;
  firstOrderOnly?: boolean;
  campaignId?: string;
  campaignName?: string;
  stackableWithPromotions?: boolean;
  stackableWithCoupons?: boolean;
  posEnabled?: boolean;
  ecommerceEnabled?: boolean;
  isSingleUse?: boolean;
  autoApply?: boolean;
  publicVisible?: boolean;
  termsAndConditions?: string;
}

export interface RedeemCouponInput {
  orderId: string;
  orderNumber: string;
  customerId?: string;
  branchId: string;
  orderAmount: number;
  discountApplied: number;
  channel: 'pos' | 'ecommerce' | 'mobile';
  ipAddress?: string;
}

export interface CouponQueryOptions extends QueryOptions {
  status?: CouponStatus;
  type?: CouponType;
  campaignId?: string;
  branchId?: string;
  activeOnly?: boolean;
  publicOnly?: boolean;
}

export class CouponService extends BaseService<Coupon> {
  constructor(
    repository: Repository<Coupon>,
    private readonly redemptionRepository: Repository<CouponRedemption>,
    private readonly dataSource: DataSource
  ) {
    super(repository);
  }

  /**
   * Generate unique coupon code
   */
  async generateCode(tenantId: string, prefix: string = 'CPN'): Promise<string> {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    let code: string;
    let exists = true;

    while (exists) {
      let random = '';
      for (let i = 0; i < 8; i++) {
        random += chars.charAt(Math.floor(Math.random() * chars.length));
      }
      code = `${prefix}-${random}`;

      const existing = await this.repository.findOne({
        where: { tenantId, code },
      });
      exists = !!existing;
    }

    return code!;
  }

  /**
   * Create a new coupon
   */
  async createCoupon(
    tenantId: string,
    input: CreateCouponInput,
    userId: string
  ): Promise<ServiceResult<Coupon>> {
    // Check for duplicate code
    const existing = await this.repository.findOne({
      where: { tenantId, code: input.code.toUpperCase() },
    });

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

    const coupon = this.repository.create({
      tenantId,
      code: input.code.toUpperCase(),
      name: input.name,
      description: input.description,
      type: input.type,
      scope: input.scope ?? CouponScope.ORDER,
      status: CouponStatus.ACTIVE,
      discountPercent: input.discountPercent,
      discountAmount: input.discountAmount,
      maxDiscount: input.maxDiscount,
      freeProductId: input.freeProductId,
      freeProductQuantity: input.freeProductQuantity ?? 1,
      validFrom: input.validFrom,
      validUntil: input.validUntil,
      maxUses: input.maxUses,
      maxUsesPerCustomer: input.maxUsesPerCustomer ?? 1,
      minOrderAmount: input.minOrderAmount,
      minItems: input.minItems,
      appliesToCategories: input.appliesToCategories,
      appliesToProducts: input.appliesToProducts,
      excludedProducts: input.excludedProducts,
      includedBranches: input.includedBranches,
      customerSpecific: input.customerSpecific ?? false,
      allowedCustomers: input.allowedCustomers,
      customerLevels: input.customerLevels,
      newCustomersOnly: input.newCustomersOnly ?? false,
      firstOrderOnly: input.firstOrderOnly ?? false,
      campaignId: input.campaignId,
      campaignName: input.campaignName,
      stackableWithPromotions: input.stackableWithPromotions ?? false,
      stackableWithCoupons: input.stackableWithCoupons ?? false,
      posEnabled: input.posEnabled ?? true,
      ecommerceEnabled: input.ecommerceEnabled ?? true,
      isSingleUse: input.isSingleUse ?? false,
      autoApply: input.autoApply ?? false,
      publicVisible: input.publicVisible ?? false,
      termsAndConditions: input.termsAndConditions,
      createdBy: userId,
    });

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

  /**
   * Generate bulk coupons for a campaign
   */
  async generateBulkCoupons(
    tenantId: string,
    baseInput: Omit<CreateCouponInput, 'code'>,
    quantity: number,
    prefix: string,
    userId: string
  ): Promise<ServiceResult<Coupon[]>> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      const coupons: Coupon[] = [];

      for (let i = 0; i < quantity; i++) {
        const code = await this.generateCode(tenantId, prefix);

        const coupon = queryRunner.manager.create(Coupon, {
          tenantId,
          code,
          ...baseInput,
          status: CouponStatus.ACTIVE,
          isSingleUse: true,
          createdBy: userId,
        });

        const saved = await queryRunner.manager.save(coupon);
        coupons.push(saved);
      }

      await queryRunner.commitTransaction();
      return { success: true, data: coupons };
    } catch (error) {
      await queryRunner.rollbackTransaction();
      return {
        success: false,
        error: {
          code: 'BULK_CREATE_ERROR',
          message: error instanceof Error ? error.message : 'Failed to create bulk coupons',
        },
      };
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * Update coupon
   */
  async updateCoupon(
    tenantId: string,
    id: string,
    input: Partial<CreateCouponInput>,
    userId: string
  ): Promise<ServiceResult<Coupon>> {
    const coupon = await this.repository.findOne({
      where: { id, tenantId },
    });

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

    // Don't allow changing code
    delete input.code;

    Object.assign(coupon, input);
    const saved = await this.repository.save(coupon);

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

  /**
   * Deactivate coupon
   */
  async deactivateCoupon(
    tenantId: string,
    id: string,
    userId: string
  ): Promise<ServiceResult<Coupon>> {
    const coupon = await this.repository.findOne({
      where: { id, tenantId },
    });

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

    coupon.status = CouponStatus.INACTIVE;
    const saved = await this.repository.save(coupon);

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

  /**
   * Record coupon redemption
   */
  async redeemCoupon(
    tenantId: string,
    couponCode: string,
    input: RedeemCouponInput,
    userId: string
  ): Promise<ServiceResult<CouponRedemption>> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      const coupon = await queryRunner.manager.findOne(Coupon, {
        where: { tenantId, code: couponCode.toUpperCase() },
      });

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

      // Check if already redeemed for this order
      const existingRedemption = await queryRunner.manager.findOne(CouponRedemption, {
        where: { tenantId, couponId: coupon.id, orderId: input.orderId },
      });

      if (existingRedemption) {
        return {
          success: false,
          error: { code: 'ALREADY_REDEEMED', message: 'Coupon already redeemed for this order' },
        };
      }

      // Check customer usage limit
      if (input.customerId && coupon.maxUsesPerCustomer) {
        const customerUses = await queryRunner.manager.count(CouponRedemption, {
          where: {
            tenantId,
            couponId: coupon.id,
            customerId: input.customerId,
            status: RedemptionStatus.COMPLETED,
          },
        });

        if (customerUses >= coupon.maxUsesPerCustomer) {
          return {
            success: false,
            error: { code: 'MAX_USES_REACHED', message: 'Customer usage limit reached' },
          };
        }
      }

      // Create redemption
      const redemption = queryRunner.manager.create(CouponRedemption, {
        tenantId,
        couponId: coupon.id,
        couponCode: coupon.code,
        orderId: input.orderId,
        orderNumber: input.orderNumber,
        customerId: input.customerId,
        branchId: input.branchId,
        status: RedemptionStatus.APPLIED,
        orderAmount: input.orderAmount,
        discountApplied: input.discountApplied,
        discountType: coupon.type,
        discountPercent: coupon.discountPercent,
        discountAmount: coupon.discountAmount,
        freeProductId: coupon.freeProductId,
        freeProductQuantity: coupon.freeProductQuantity,
        channel: input.channel,
        ipAddress: input.ipAddress,
        appliedBy: userId,
      });

      const savedRedemption = await queryRunner.manager.save(redemption);

      // Increment coupon usage
      coupon.currentUses += 1;

      // Check if depleted
      if (coupon.maxUses && coupon.currentUses >= coupon.maxUses) {
        coupon.status = CouponStatus.DEPLETED;
      }

      // Mark as inactive if single use
      if (coupon.isSingleUse) {
        coupon.status = CouponStatus.DEPLETED;
      }

      await queryRunner.manager.save(coupon);
      await queryRunner.commitTransaction();

      return { success: true, data: savedRedemption };
    } catch (error) {
      await queryRunner.rollbackTransaction();
      return {
        success: false,
        error: {
          code: 'REDEEM_ERROR',
          message: error instanceof Error ? error.message : 'Failed to redeem coupon',
        },
      };
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * Complete redemption (after order is finalized)
   */
  async completeRedemption(
    tenantId: string,
    redemptionId: string
  ): Promise<ServiceResult<CouponRedemption>> {
    const redemption = await this.redemptionRepository.findOne({
      where: { id: redemptionId, tenantId },
    });

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

    redemption.status = RedemptionStatus.COMPLETED;
    const saved = await this.redemptionRepository.save(redemption);

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

  /**
   * Reverse redemption (e.g., order cancelled)
   */
  async reverseRedemption(
    tenantId: string,
    redemptionId: string,
    reason: string,
    userId: string
  ): Promise<ServiceResult<CouponRedemption>> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      const redemption = await queryRunner.manager.findOne(CouponRedemption, {
        where: { id: redemptionId, tenantId },
      });

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

      if (redemption.status === RedemptionStatus.REVERSED) {
        return {
          success: false,
          error: { code: 'ALREADY_REVERSED', message: 'Redemption is already reversed' },
        };
      }

      redemption.status = RedemptionStatus.REVERSED;
      redemption.reversedAt = new Date();
      redemption.reversedBy = userId;
      redemption.reversalReason = reason;

      await queryRunner.manager.save(redemption);

      // Restore coupon usage
      const coupon = await queryRunner.manager.findOne(Coupon, {
        where: { id: redemption.couponId, tenantId },
      });

      if (coupon) {
        coupon.currentUses = Math.max(0, coupon.currentUses - 1);

        // Reactivate if was depleted
        if (coupon.status === CouponStatus.DEPLETED && !coupon.isSingleUse) {
          coupon.status = CouponStatus.ACTIVE;
        }

        await queryRunner.manager.save(coupon);
      }

      await queryRunner.commitTransaction();
      return { success: true, data: redemption };
    } catch (error) {
      await queryRunner.rollbackTransaction();
      return {
        success: false,
        error: {
          code: 'REVERSE_ERROR',
          message: error instanceof Error ? error.message : 'Failed to reverse redemption',
        },
      };
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * Find coupons with filters
   */
  async findCoupons(
    tenantId: string,
    options: CouponQueryOptions
  ): Promise<{ data: Coupon[]; total: number }> {
    const qb = this.repository.createQueryBuilder('coupon')
      .where('coupon.tenantId = :tenantId', { tenantId });

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

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

    if (options.campaignId) {
      qb.andWhere('coupon.campaignId = :campaignId', { campaignId: options.campaignId });
    }

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

    if (options.activeOnly) {
      const now = new Date();
      qb.andWhere('coupon.status = :activeStatus', { activeStatus: CouponStatus.ACTIVE })
        .andWhere('coupon.validFrom <= :now', { now })
        .andWhere('(coupon.validUntil IS NULL OR coupon.validUntil >= :now)', { now });
    }

    if (options.publicOnly) {
      qb.andWhere('coupon.publicVisible = true');
    }

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

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

  /**
   * Get coupon by code
   */
  async getCouponByCode(
    tenantId: string,
    code: string
  ): Promise<Coupon | null> {
    return this.repository.findOne({
      where: { tenantId, code: code.toUpperCase() },
    });
  }

  /**
   * Get redemption history for a coupon
   */
  async getRedemptionHistory(
    tenantId: string,
    couponId: string,
    options?: { page?: number; limit?: number }
  ): Promise<{ data: CouponRedemption[]; total: number }> {
    const page = options?.page ?? 1;
    const limit = options?.limit ?? 20;

    const [data, total] = await this.redemptionRepository.findAndCount({
      where: { tenantId, couponId },
      order: { createdAt: 'DESC' },
      skip: (page - 1) * limit,
      take: limit,
    });

    return { data, total };
  }

  /**
   * Get customer redemption history
   */
  async getCustomerRedemptions(
    tenantId: string,
    customerId: string,
    options?: { page?: number; limit?: number }
  ): Promise<{ data: CouponRedemption[]; total: number }> {
    const page = options?.page ?? 1;
    const limit = options?.limit ?? 20;

    const [data, total] = await this.redemptionRepository.findAndCount({
      where: { tenantId, customerId },
      relations: ['coupon'],
      order: { createdAt: 'DESC' },
      skip: (page - 1) * limit,
      take: limit,
    });

    return { data, total };
  }

  /**
   * Get coupon statistics
   */
  async getCouponStats(
    tenantId: string,
    couponId: string
  ): Promise<{
    totalRedemptions: number;
    completedRedemptions: number;
    reversedRedemptions: number;
    totalDiscountGiven: number;
    remainingUses: number | null;
  }> {
    const coupon = await this.repository.findOne({
      where: { id: couponId, tenantId },
    });

    if (!coupon) {
      return {
        totalRedemptions: 0,
        completedRedemptions: 0,
        reversedRedemptions: 0,
        totalDiscountGiven: 0,
        remainingUses: null,
      };
    }

    const redemptions = await this.redemptionRepository.find({
      where: { tenantId, couponId },
    });

    const completedRedemptions = redemptions.filter(
      r => r.status === RedemptionStatus.COMPLETED
    );

    const totalDiscountGiven = completedRedemptions.reduce(
      (sum, r) => sum + Number(r.discountApplied),
      0
    );

    return {
      totalRedemptions: redemptions.length,
      completedRedemptions: completedRedemptions.length,
      reversedRedemptions: redemptions.filter(r => r.status === RedemptionStatus.REVERSED).length,
      totalDiscountGiven,
      remainingUses: coupon.maxUses ? coupon.maxUses - coupon.currentUses : null,
    };
  }

  /**
   * Expire outdated coupons
   */
  async expireOutdatedCoupons(tenantId: string): Promise<number> {
    const now = new Date();

    const result = await this.repository.update(
      {
        tenantId,
        status: CouponStatus.ACTIVE,
        validUntil: now,
      },
      {
        status: CouponStatus.EXPIRED,
      }
    );

    return result.affected ?? 0;
  }
}
