import { Repository, DataSource, Between } from 'typeorm';
import { BaseService } from '../../../shared/services/base.service';
import { ServiceResult, QueryOptions } from '../../../shared/types';
import {
  StockAdjustment,
  AdjustmentStatus,
  AdjustmentType,
} from '../entities/stock-adjustment.entity';
import { StockAdjustmentLine, AdjustmentDirection } from '../entities/stock-adjustment-line.entity';

export interface AdjustmentLineInput {
  productId: string;
  productCode: string;
  productName: string;
  productBarcode?: string;
  variantId?: string;
  variantName?: string;
  uomId?: string;
  uomName?: string;
  systemQuantity: number;
  countedQuantity: number;
  unitCost?: number;
  locationId?: string;
  locationCode?: string;
  lotNumber?: string;
  serialNumber?: string;
  expiryDate?: Date;
  lineReason?: string;
  notes?: string;
}

export interface CreateAdjustmentInput {
  branchId: string;
  warehouseId: string;
  locationId?: string;
  type: AdjustmentType;
  adjustmentDate: Date;
  isFullCount?: boolean;
  countCategoryId?: string;
  reason: string;
  notes?: string;
  lines: AdjustmentLineInput[];
}

export interface AdjustmentQueryOptions extends QueryOptions {
  branchId?: string;
  warehouseId?: string;
  status?: AdjustmentStatus;
  type?: AdjustmentType;
  startDate?: Date;
  endDate?: Date;
}

export class StockAdjustmentService extends BaseService<StockAdjustment> {
  constructor(
    repository: Repository<StockAdjustment>,
    private readonly lineRepository: Repository<StockAdjustmentLine>,
    private readonly dataSource: DataSource
  ) {
    super(repository);
  }

  /**
   * Generate adjustment number
   */
  private async generateAdjustmentNumber(tenantId: string, type: AdjustmentType): Promise<string> {
    const today = new Date();
    const datePrefix = today.toISOString().slice(0, 10).replace(/-/g, '');
    const typePrefix = type === AdjustmentType.INVENTORY_COUNT ? 'CNT' : 'ADJ';

    const count = await this.repository.count({
      where: {
        tenantId,
        createdAt: Between(
          new Date(today.setHours(0, 0, 0, 0)),
          new Date(today.setHours(23, 59, 59, 999))
        ),
      },
    });

    return `${typePrefix}-${datePrefix}-${String(count + 1).padStart(4, '0')}`;
  }

  /**
   * Create a new stock adjustment
   */
  async createAdjustment(
    tenantId: string,
    input: CreateAdjustmentInput,
    userId: string
  ): Promise<ServiceResult<StockAdjustment>> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      const number = await this.generateAdjustmentNumber(tenantId, input.type);

      // Calculate totals
      let totalIncreaseQty = 0;
      let totalDecreaseQty = 0;
      let totalIncreaseValue = 0;
      let totalDecreaseValue = 0;

      for (const line of input.lines) {
        const adjustmentQty = line.countedQuantity - line.systemQuantity;
        const unitCost = line.unitCost ?? 0;

        if (adjustmentQty > 0) {
          totalIncreaseQty += adjustmentQty;
          totalIncreaseValue += adjustmentQty * unitCost;
        } else {
          totalDecreaseQty += Math.abs(adjustmentQty);
          totalDecreaseValue += Math.abs(adjustmentQty) * unitCost;
        }
      }

      const netAdjustmentValue = totalIncreaseValue - totalDecreaseValue;

      // Determine if approval is required
      const approvalThreshold = 10000; // Could be configurable
      const requiresApproval = Math.abs(netAdjustmentValue) >= approvalThreshold;

      const adjustment = queryRunner.manager.create(StockAdjustment, {
        tenantId,
        branchId: input.branchId,
        warehouseId: input.warehouseId,
        locationId: input.locationId,
        number,
        type: input.type,
        status: AdjustmentStatus.DRAFT,
        adjustmentDate: input.adjustmentDate,
        isFullCount: input.isFullCount ?? false,
        countCategoryId: input.countCategoryId,
        linesCount: input.lines.length,
        totalIncreaseQty,
        totalDecreaseQty,
        totalIncreaseValue,
        totalDecreaseValue,
        netAdjustmentValue,
        requiresApproval,
        approvalThreshold: requiresApproval ? approvalThreshold : null,
        reason: input.reason,
        notes: input.notes,
        createdBy: userId,
      });

      const savedAdjustment = await queryRunner.manager.save(adjustment);

      // Create lines
      let lineNumber = 1;
      for (const lineInput of input.lines) {
        const adjustmentQuantity = lineInput.countedQuantity - lineInput.systemQuantity;
        const direction = adjustmentQuantity >= 0 ? AdjustmentDirection.INCREASE : AdjustmentDirection.DECREASE;
        const unitCost = lineInput.unitCost ?? 0;

        const line = queryRunner.manager.create(StockAdjustmentLine, {
          tenantId,
          adjustmentId: savedAdjustment.id,
          lineNumber: lineNumber++,
          productId: lineInput.productId,
          productCode: lineInput.productCode,
          productName: lineInput.productName,
          productBarcode: lineInput.productBarcode,
          variantId: lineInput.variantId,
          variantName: lineInput.variantName,
          uomId: lineInput.uomId,
          uomName: lineInput.uomName ?? 'PZA',
          systemQuantity: lineInput.systemQuantity,
          countedQuantity: lineInput.countedQuantity,
          adjustmentQuantity: Math.abs(adjustmentQuantity),
          direction,
          unitCost,
          adjustmentValue: Math.abs(adjustmentQuantity) * unitCost,
          locationId: lineInput.locationId,
          locationCode: lineInput.locationCode,
          lotNumber: lineInput.lotNumber,
          serialNumber: lineInput.serialNumber,
          expiryDate: lineInput.expiryDate,
          lineReason: lineInput.lineReason,
          notes: lineInput.notes,
        });

        await queryRunner.manager.save(line);
      }

      await queryRunner.commitTransaction();

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

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

  /**
   * Submit adjustment for approval
   */
  async submitForApproval(
    tenantId: string,
    id: string,
    userId: string
  ): Promise<ServiceResult<StockAdjustment>> {
    const adjustment = await this.repository.findOne({
      where: { id, tenantId },
    });

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

    if (adjustment.status !== AdjustmentStatus.DRAFT) {
      return {
        success: false,
        error: { code: 'INVALID_STATUS', message: 'Adjustment is not in draft status' },
      };
    }

    adjustment.status = adjustment.requiresApproval
      ? AdjustmentStatus.PENDING_APPROVAL
      : AdjustmentStatus.APPROVED;

    if (!adjustment.requiresApproval) {
      adjustment.approvedBy = userId;
      adjustment.approvedAt = new Date();
    }

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

  /**
   * Approve adjustment
   */
  async approveAdjustment(
    tenantId: string,
    id: string,
    approverId: string
  ): Promise<ServiceResult<StockAdjustment>> {
    const adjustment = await this.repository.findOne({
      where: { id, tenantId },
    });

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

    if (adjustment.status !== AdjustmentStatus.PENDING_APPROVAL) {
      return {
        success: false,
        error: { code: 'INVALID_STATUS', message: 'Adjustment is not pending approval' },
      };
    }

    adjustment.status = AdjustmentStatus.APPROVED;
    adjustment.approvedBy = approverId;
    adjustment.approvedAt = new Date();

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

  /**
   * Reject adjustment
   */
  async rejectAdjustment(
    tenantId: string,
    id: string,
    rejecterId: string,
    reason: string
  ): Promise<ServiceResult<StockAdjustment>> {
    const adjustment = await this.repository.findOne({
      where: { id, tenantId },
    });

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

    if (adjustment.status !== AdjustmentStatus.PENDING_APPROVAL) {
      return {
        success: false,
        error: { code: 'INVALID_STATUS', message: 'Adjustment is not pending approval' },
      };
    }

    adjustment.status = AdjustmentStatus.REJECTED;
    adjustment.rejectedBy = rejecterId;
    adjustment.rejectedAt = new Date();
    adjustment.rejectionReason = reason;

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

  /**
   * Post adjustment (apply to inventory)
   */
  async postAdjustment(
    tenantId: string,
    id: string,
    userId: string
  ): Promise<ServiceResult<StockAdjustment>> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      const adjustment = await queryRunner.manager.findOne(StockAdjustment, {
        where: { id, tenantId },
        relations: ['lines'],
      });

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

      if (adjustment.status !== AdjustmentStatus.APPROVED) {
        return {
          success: false,
          error: { code: 'INVALID_STATUS', message: 'Adjustment must be approved before posting' },
        };
      }

      // TODO: Update actual inventory stock levels
      // This would involve updating stock records in the inventory system
      // For each line:
      // - If direction is INCREASE, add adjustmentQuantity to stock
      // - If direction is DECREASE, subtract adjustmentQuantity from stock

      adjustment.status = AdjustmentStatus.POSTED;
      adjustment.postedBy = userId;
      adjustment.postedAt = new Date();

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

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

  /**
   * Cancel adjustment
   */
  async cancelAdjustment(
    tenantId: string,
    id: string,
    reason: string,
    userId: string
  ): Promise<ServiceResult<StockAdjustment>> {
    const adjustment = await this.repository.findOne({
      where: { id, tenantId },
    });

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

    if ([AdjustmentStatus.POSTED, AdjustmentStatus.CANCELLED].includes(adjustment.status)) {
      return {
        success: false,
        error: { code: 'INVALID_STATUS', message: 'Cannot cancel a posted or already cancelled adjustment' },
      };
    }

    adjustment.status = AdjustmentStatus.CANCELLED;
    adjustment.notes = `${adjustment.notes ?? ''}\nCancelled: ${reason}`.trim();

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

  /**
   * Find adjustments with filters
   */
  async findAdjustments(
    tenantId: string,
    options: AdjustmentQueryOptions
  ): Promise<{ data: StockAdjustment[]; total: number }> {
    const qb = this.repository.createQueryBuilder('adjustment')
      .where('adjustment.tenantId = :tenantId', { tenantId });

    if (options.branchId) {
      qb.andWhere('adjustment.branchId = :branchId', { branchId: options.branchId });
    }
    if (options.warehouseId) {
      qb.andWhere('adjustment.warehouseId = :warehouseId', { warehouseId: options.warehouseId });
    }
    if (options.status) {
      qb.andWhere('adjustment.status = :status', { status: options.status });
    }
    if (options.type) {
      qb.andWhere('adjustment.type = :type', { type: options.type });
    }
    if (options.startDate && options.endDate) {
      qb.andWhere('adjustment.adjustmentDate 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('adjustment.adjustmentDate', 'DESC');

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

  /**
   * Get adjustment with lines
   */
  async getAdjustmentWithLines(
    tenantId: string,
    id: string
  ): Promise<StockAdjustment | null> {
    return this.repository.findOne({
      where: { id, tenantId },
      relations: ['lines'],
    });
  }

  /**
   * Get adjustment summary by type and status
   */
  async getAdjustmentSummary(
    tenantId: string,
    branchId?: string,
    startDate?: Date,
    endDate?: Date
  ): Promise<{
    byType: Record<AdjustmentType, { count: number; value: number }>;
    byStatus: Record<AdjustmentStatus, number>;
    totalIncreaseValue: number;
    totalDecreaseValue: number;
    netValue: number;
  }> {
    const qb = this.repository.createQueryBuilder('adjustment')
      .where('adjustment.tenantId = :tenantId', { tenantId });

    if (branchId) {
      qb.andWhere('adjustment.branchId = :branchId', { branchId });
    }
    if (startDate && endDate) {
      qb.andWhere('adjustment.adjustmentDate BETWEEN :startDate AND :endDate', {
        startDate,
        endDate,
      });
    }

    const adjustments = await qb.getMany();

    const byType: Record<string, { count: number; value: number }> = {};
    const byStatus: Record<string, number> = {};
    let totalIncreaseValue = 0;
    let totalDecreaseValue = 0;

    for (const adj of adjustments) {
      // By type
      if (!byType[adj.type]) {
        byType[adj.type] = { count: 0, value: 0 };
      }
      byType[adj.type].count++;
      byType[adj.type].value += Number(adj.netAdjustmentValue);

      // By status
      byStatus[adj.status] = (byStatus[adj.status] ?? 0) + 1;

      // Totals
      totalIncreaseValue += Number(adj.totalIncreaseValue);
      totalDecreaseValue += Number(adj.totalDecreaseValue);
    }

    return {
      byType: byType as Record<AdjustmentType, { count: number; value: number }>,
      byStatus: byStatus as Record<AdjustmentStatus, number>,
      totalIncreaseValue,
      totalDecreaseValue,
      netValue: totalIncreaseValue - totalDecreaseValue,
    };
  }

  /**
   * Add line to existing adjustment
   */
  async addLine(
    tenantId: string,
    adjustmentId: string,
    lineInput: AdjustmentLineInput,
    userId: string
  ): Promise<ServiceResult<StockAdjustmentLine>> {
    const adjustment = await this.repository.findOne({
      where: { id: adjustmentId, tenantId },
    });

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

    if (adjustment.status !== AdjustmentStatus.DRAFT) {
      return {
        success: false,
        error: { code: 'INVALID_STATUS', message: 'Can only add lines to draft adjustments' },
      };
    }

    const lastLine = await this.lineRepository.findOne({
      where: { adjustmentId, tenantId },
      order: { lineNumber: 'DESC' },
    });

    const adjustmentQuantity = lineInput.countedQuantity - lineInput.systemQuantity;
    const direction = adjustmentQuantity >= 0 ? AdjustmentDirection.INCREASE : AdjustmentDirection.DECREASE;
    const unitCost = lineInput.unitCost ?? 0;

    const line = this.lineRepository.create({
      tenantId,
      adjustmentId,
      lineNumber: (lastLine?.lineNumber ?? 0) + 1,
      productId: lineInput.productId,
      productCode: lineInput.productCode,
      productName: lineInput.productName,
      productBarcode: lineInput.productBarcode,
      variantId: lineInput.variantId,
      variantName: lineInput.variantName,
      uomId: lineInput.uomId,
      uomName: lineInput.uomName ?? 'PZA',
      systemQuantity: lineInput.systemQuantity,
      countedQuantity: lineInput.countedQuantity,
      adjustmentQuantity: Math.abs(adjustmentQuantity),
      direction,
      unitCost,
      adjustmentValue: Math.abs(adjustmentQuantity) * unitCost,
      locationId: lineInput.locationId,
      locationCode: lineInput.locationCode,
      lotNumber: lineInput.lotNumber,
      serialNumber: lineInput.serialNumber,
      expiryDate: lineInput.expiryDate,
      lineReason: lineInput.lineReason,
      notes: lineInput.notes,
    });

    const saved = await this.lineRepository.save(line);

    // Update adjustment totals
    await this.recalculateTotals(tenantId, adjustmentId);

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

  /**
   * Recalculate adjustment totals from lines
   */
  private async recalculateTotals(tenantId: string, adjustmentId: string): Promise<void> {
    const lines = await this.lineRepository.find({
      where: { adjustmentId, tenantId },
    });

    let totalIncreaseQty = 0;
    let totalDecreaseQty = 0;
    let totalIncreaseValue = 0;
    let totalDecreaseValue = 0;

    for (const line of lines) {
      if (line.direction === AdjustmentDirection.INCREASE) {
        totalIncreaseQty += Number(line.adjustmentQuantity);
        totalIncreaseValue += Number(line.adjustmentValue);
      } else {
        totalDecreaseQty += Number(line.adjustmentQuantity);
        totalDecreaseValue += Number(line.adjustmentValue);
      }
    }

    await this.repository.update(
      { id: adjustmentId, tenantId },
      {
        linesCount: lines.length,
        totalIncreaseQty,
        totalDecreaseQty,
        totalIncreaseValue,
        totalDecreaseValue,
        netAdjustmentValue: totalIncreaseValue - totalDecreaseValue,
      }
    );
  }
}
