import { Repository, DataSource, In } from 'typeorm';
import { BaseService } from '../../../shared/services/base.service';
import { ServiceResult, PaginatedResult } from '../../../shared/types';
import {
  PurchaseSuggestion,
  SuggestionStatus,
  SuggestionReason,
  SupplierOrder,
  SupplierOrderLine,
} from '../entities';
import {
  CreateSuggestionInput,
  UpdateSuggestionInput,
  ReviewSuggestionInput,
  ConvertSuggestionsInput,
  GenerateSuggestionsInput,
} from '../validation/purchases.schema';
import { SupplierOrderService } from './supplier-order.service';

export class PurchaseSuggestionService extends BaseService<PurchaseSuggestion> {
  constructor(
    private dataSource: DataSource,
    private supplierOrderService: SupplierOrderService
  ) {
    super(dataSource.getRepository(PurchaseSuggestion));
  }

  /**
   * Create manual suggestion
   */
  async createSuggestion(
    tenantId: string,
    data: CreateSuggestionInput,
    userId: string
  ): Promise<ServiceResult<PurchaseSuggestion>> {
    try {
      const suggestion = this.repository.create({
        tenantId,
        branchId: data.branchId,
        warehouseId: data.warehouseId,
        status: SuggestionStatus.PENDING,
        reason: data.reason || SuggestionReason.MANUAL,
        productId: data.productId,
        productCode: data.productCode,
        productName: data.productName,
        supplierId: data.supplierId,
        supplierName: data.supplierName,
        suggestedQuantity: data.suggestedQuantity,
        estimatedUnitCost: data.estimatedUnitCost,
        estimatedTotalCost: data.estimatedUnitCost
          ? data.estimatedUnitCost * data.suggestedQuantity
          : undefined,
        priority: data.priority || 0,
        dueDate: data.dueDate,
        notes: data.notes,
        calculationData: {
          algorithm: 'manual',
          salesPeriod: 0,
          salesTotal: 0,
        },
        expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days
      });

      const saved = await this.repository.save(suggestion);
      return { success: true, data: saved };
    } catch (error: any) {
      return {
        success: false,
        error: {
          code: 'CREATE_SUGGESTION_FAILED',
          message: error.message || 'Failed to create suggestion',
          details: error,
        },
      };
    }
  }

  /**
   * Update suggestion
   */
  async updateSuggestion(
    tenantId: string,
    suggestionId: string,
    data: UpdateSuggestionInput,
    userId: string
  ): Promise<ServiceResult<PurchaseSuggestion>> {
    const suggestion = await this.findById(tenantId, suggestionId);
    if (!suggestion) {
      return {
        success: false,
        error: { code: 'NOT_FOUND', message: 'Suggestion not found' },
      };
    }

    if (suggestion.status !== SuggestionStatus.PENDING) {
      return {
        success: false,
        error: { code: 'INVALID_STATUS', message: 'Only pending suggestions can be updated' },
      };
    }

    Object.assign(suggestion, data);

    // Recalculate estimated total if unit cost or quantity changed
    if (data.estimatedUnitCost !== undefined || data.suggestedQuantity !== undefined) {
      const unitCost = data.estimatedUnitCost ?? suggestion.estimatedUnitCost;
      const quantity = data.suggestedQuantity ?? suggestion.suggestedQuantity;
      if (unitCost) {
        suggestion.estimatedTotalCost = unitCost * quantity;
      }
    }

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

  /**
   * Review suggestion (approve, reject, or modify)
   */
  async reviewSuggestion(
    tenantId: string,
    suggestionId: string,
    userId: string,
    review: ReviewSuggestionInput
  ): Promise<ServiceResult<PurchaseSuggestion>> {
    const suggestion = await this.findById(tenantId, suggestionId);
    if (!suggestion) {
      return {
        success: false,
        error: { code: 'NOT_FOUND', message: 'Suggestion not found' },
      };
    }

    if (suggestion.status !== SuggestionStatus.PENDING && suggestion.status !== SuggestionStatus.REVIEWED) {
      return {
        success: false,
        error: { code: 'INVALID_STATUS', message: 'Suggestion cannot be reviewed in current status' },
      };
    }

    suggestion.reviewedBy = userId;
    suggestion.reviewedAt = new Date();
    if (review.notes) {
      suggestion.reviewNotes = review.notes;
    }

    switch (review.action) {
      case 'approve':
        suggestion.status = SuggestionStatus.APPROVED;
        suggestion.approvedQuantity = review.approvedQuantity ?? suggestion.suggestedQuantity;
        if (review.supplierId) {
          suggestion.supplierId = review.supplierId;
        }
        break;

      case 'reject':
        suggestion.status = SuggestionStatus.REJECTED;
        break;

      case 'modify':
        suggestion.status = SuggestionStatus.REVIEWED;
        if (review.approvedQuantity) {
          suggestion.approvedQuantity = review.approvedQuantity;
        }
        if (review.supplierId) {
          suggestion.supplierId = review.supplierId;
        }
        break;
    }

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

  /**
   * Bulk review suggestions
   */
  async bulkReviewSuggestions(
    tenantId: string,
    suggestionIds: string[],
    userId: string,
    action: 'approve' | 'reject',
    notes?: string
  ): Promise<ServiceResult<{ processed: number; failed: string[] }>> {
    const suggestions = await this.repository.find({
      where: {
        tenantId,
        id: In(suggestionIds),
        status: In([SuggestionStatus.PENDING, SuggestionStatus.REVIEWED]),
      },
    });

    const failed: string[] = [];
    let processed = 0;

    for (const suggestion of suggestions) {
      try {
        suggestion.reviewedBy = userId;
        suggestion.reviewedAt = new Date();
        suggestion.reviewNotes = notes;

        if (action === 'approve') {
          suggestion.status = SuggestionStatus.APPROVED;
          suggestion.approvedQuantity = suggestion.suggestedQuantity;
        } else {
          suggestion.status = SuggestionStatus.REJECTED;
        }

        await this.repository.save(suggestion);
        processed++;
      } catch (error) {
        failed.push(suggestion.id);
      }
    }

    // Add IDs that weren't found
    const foundIds = suggestions.map(s => s.id);
    for (const id of suggestionIds) {
      if (!foundIds.includes(id) && !failed.includes(id)) {
        failed.push(id);
      }
    }

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

  /**
   * Convert approved suggestions to supplier orders
   */
  async convertToOrders(
    tenantId: string,
    data: ConvertSuggestionsInput,
    userId: string
  ): Promise<ServiceResult<SupplierOrder[]>> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // Get approved suggestions
      const suggestions = await this.repository.find({
        where: {
          tenantId,
          id: In(data.suggestionIds),
          status: SuggestionStatus.APPROVED,
        },
      });

      if (suggestions.length === 0) {
        return {
          success: false,
          error: { code: 'NO_SUGGESTIONS', message: 'No approved suggestions found' },
        };
      }

      const orders: SupplierOrder[] = [];

      if (data.groupBySupplier) {
        // Group by supplier
        const bySupplier = new Map<string, PurchaseSuggestion[]>();
        for (const suggestion of suggestions) {
          const key = suggestion.supplierId || 'no_supplier';
          const existing = bySupplier.get(key) || [];
          existing.push(suggestion);
          bySupplier.set(key, existing);
        }

        // Create one order per supplier
        for (const [supplierId, supplierSuggestions] of bySupplier.entries()) {
          if (supplierId === 'no_supplier') {
            // Skip suggestions without supplier
            continue;
          }

          const firstSuggestion = supplierSuggestions[0];
          const lines = supplierSuggestions.map(s => ({
            productId: s.productId,
            productCode: s.productCode,
            productName: s.productName,
            quantityOrdered: s.approvedQuantity || s.suggestedQuantity,
            unitPrice: s.estimatedUnitCost || 0,
            taxRate: 0.16,
          }));

          const orderResult = await this.supplierOrderService.createOrder(
            tenantId,
            {
              branchId: firstSuggestion.branchId,
              warehouseId: firstSuggestion.warehouseId,
              supplierId,
              supplierName: firstSuggestion.supplierName || '',
              orderDate: new Date(),
              expectedDate: data.expectedDate,
              lines,
            },
            userId
          );

          if (orderResult.success && orderResult.data) {
            orders.push(orderResult.data);

            // Update suggestions with order reference
            for (const suggestion of supplierSuggestions) {
              suggestion.status = SuggestionStatus.ORDERED;
              suggestion.supplierOrderId = orderResult.data.id;
              suggestion.supplierOrderNumber = orderResult.data.number;
              suggestion.orderedQuantity = suggestion.approvedQuantity || suggestion.suggestedQuantity;
            }
            await queryRunner.manager.save(supplierSuggestions);
          }
        }
      } else {
        // Create individual orders for each suggestion
        for (const suggestion of suggestions) {
          if (!suggestion.supplierId) continue;

          const orderResult = await this.supplierOrderService.createOrder(
            tenantId,
            {
              branchId: suggestion.branchId,
              warehouseId: suggestion.warehouseId,
              supplierId: suggestion.supplierId,
              supplierName: suggestion.supplierName || '',
              orderDate: new Date(),
              expectedDate: data.expectedDate,
              lines: [{
                productId: suggestion.productId,
                productCode: suggestion.productCode,
                productName: suggestion.productName,
                quantityOrdered: suggestion.approvedQuantity || suggestion.suggestedQuantity,
                unitPrice: suggestion.estimatedUnitCost || 0,
                taxRate: 0.16,
              }],
            },
            userId
          );

          if (orderResult.success && orderResult.data) {
            orders.push(orderResult.data);

            suggestion.status = SuggestionStatus.ORDERED;
            suggestion.supplierOrderId = orderResult.data.id;
            suggestion.supplierOrderNumber = orderResult.data.number;
            suggestion.orderedQuantity = suggestion.approvedQuantity || suggestion.suggestedQuantity;
            await queryRunner.manager.save(suggestion);
          }
        }
      }

      await queryRunner.commitTransaction();

      return { success: true, data: orders };
    } catch (error: any) {
      await queryRunner.rollbackTransaction();
      return {
        success: false,
        error: {
          code: 'CONVERT_TO_ORDERS_FAILED',
          message: error.message || 'Failed to convert suggestions to orders',
          details: error,
        },
      };
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * Generate purchase suggestions based on stock levels
   */
  async generateSuggestions(
    tenantId: string,
    branchId: string,
    warehouseId: string,
    data: GenerateSuggestionsInput,
    userId: string
  ): Promise<ServiceResult<{ generated: number; suggestions: PurchaseSuggestion[] }>> {
    // This is a simplified implementation
    // In a real scenario, this would:
    // 1. Query inventory levels from the inventory module
    // 2. Compare against reorder points and min stock levels
    // 3. Optionally use sales forecast data
    // 4. Generate suggestions for items below threshold

    try {
      // TODO: Integrate with inventory module to get actual stock data
      // For now, return a placeholder response

      // Mark expired suggestions
      await this.repository
        .createQueryBuilder()
        .update(PurchaseSuggestion)
        .set({ status: SuggestionStatus.EXPIRED })
        .where('tenantId = :tenantId', { tenantId })
        .andWhere('branchId = :branchId', { branchId })
        .andWhere('status = :status', { status: SuggestionStatus.PENDING })
        .andWhere('expiresAt < :now', { now: new Date() })
        .execute();

      return {
        success: true,
        data: {
          generated: 0,
          suggestions: [],
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: {
          code: 'GENERATE_SUGGESTIONS_FAILED',
          message: error.message || 'Failed to generate suggestions',
          details: error,
        },
      };
    }
  }

  /**
   * List suggestions with filters
   */
  async listSuggestions(
    tenantId: string,
    branchId: string,
    query: {
      page?: number;
      limit?: number;
      status?: SuggestionStatus;
      reason?: SuggestionReason;
      supplierId?: string;
      priority?: number;
      startDate?: Date;
      endDate?: Date;
      search?: string;
    }
  ): Promise<PaginatedResult<PurchaseSuggestion>> {
    const {
      page = 1,
      limit = 20,
      status,
      reason,
      supplierId,
      priority,
      startDate,
      endDate,
      search,
    } = query;

    const qb = this.repository.createQueryBuilder('suggestion')
      .where('suggestion.tenantId = :tenantId', { tenantId })
      .andWhere('suggestion.branchId = :branchId', { branchId });

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

    if (reason) {
      qb.andWhere('suggestion.reason = :reason', { reason });
    }

    if (supplierId) {
      qb.andWhere('suggestion.supplierId = :supplierId', { supplierId });
    }

    if (priority !== undefined) {
      qb.andWhere('suggestion.priority = :priority', { priority });
    }

    if (startDate) {
      qb.andWhere('suggestion.createdAt >= :startDate', { startDate });
    }

    if (endDate) {
      qb.andWhere('suggestion.createdAt <= :endDate', { endDate });
    }

    if (search) {
      qb.andWhere(
        '(suggestion.productCode ILIKE :search OR suggestion.productName ILIKE :search)',
        { search: `%${search}%` }
      );
    }

    const total = await qb.getCount();

    qb.orderBy('suggestion.priority', 'DESC')
      .addOrderBy('suggestion.createdAt', 'DESC')
      .skip((page - 1) * limit)
      .take(limit);

    const data = await qb.getMany();
    const totalPages = Math.ceil(total / limit);

    return {
      data,
      pagination: {
        page,
        limit,
        total,
        totalPages,
        hasNext: page < totalPages,
        hasPrev: page > 1,
      },
    };
  }

  /**
   * Get suggestion statistics
   */
  async getSuggestionStats(
    tenantId: string,
    branchId: string
  ): Promise<{
    pending: number;
    approved: number;
    rejected: number;
    ordered: number;
    byPriority: Record<number, number>;
    byReason: Record<string, number>;
    totalEstimatedValue: number;
  }> {
    const suggestions = await this.repository.find({
      where: { tenantId, branchId },
    });

    const pending = suggestions.filter(s => s.status === SuggestionStatus.PENDING).length;
    const approved = suggestions.filter(s => s.status === SuggestionStatus.APPROVED).length;
    const rejected = suggestions.filter(s => s.status === SuggestionStatus.REJECTED).length;
    const ordered = suggestions.filter(s => s.status === SuggestionStatus.ORDERED).length;

    const byPriority: Record<number, number> = { 0: 0, 1: 0, 2: 0, 3: 0 };
    const byReason: Record<string, number> = {};
    let totalEstimatedValue = 0;

    for (const suggestion of suggestions) {
      if (suggestion.status === SuggestionStatus.PENDING || suggestion.status === SuggestionStatus.APPROVED) {
        byPriority[suggestion.priority] = (byPriority[suggestion.priority] || 0) + 1;
        byReason[suggestion.reason] = (byReason[suggestion.reason] || 0) + 1;
        if (suggestion.estimatedTotalCost) {
          totalEstimatedValue += suggestion.estimatedTotalCost;
        }
      }
    }

    return {
      pending,
      approved,
      rejected,
      ordered,
      byPriority,
      byReason,
      totalEstimatedValue: Math.round(totalEstimatedValue * 100) / 100,
    };
  }
}
