import type { Injector } from 'graphql-modules';
import { DocumentType } from '../../../shared/enums.js';
import type { IGetIssuedDocumentsStatusByChargeIdsResult } from '../../documents/__generated__/issued-documents.types.js';
import { IssuedDocumentsProvider } from '../../documents/providers/issued-documents.provider.js';
import { ClientsProvider } from '../../financial-entities/providers/clients.provider.js';
import { calculateAmountConfidence } from '../helpers/amount-confidence.helper.js';
import { calculateBusinessConfidence } from '../helpers/business-confidence.helper.js';
import { calculateCurrencyConfidence } from '../helpers/currency-confidence.helper.js';
import { calculateDateConfidence } from '../helpers/date-confidence.helper.js';
import { calculateOverallConfidence } from '../helpers/overall-confidence.helper.js';
import type {
  AggregatedDocument,
  AggregatedTransaction,
  ConfidenceScores,
  DocumentCharge,
  MatchScore,
  TransactionCharge,
} from '../types.js';
import { aggregateDocuments } from './document-aggregator.js';
import { aggregateTransactions } from './transaction-aggregator.js';

/**
 * Select appropriate transaction date based on document type
 * Per specification:
 * - INVOICE/CREDIT_INVOICE: Use event_date
 * - RECEIPT/INVOICE_RECEIPT: Use debit_date (debit_timestamp or debit_date)
 * - OTHER/PROFORMA/UNPROCESSED: Calculate both and use better score
 *
 * @param transaction - Aggregated transaction data
 * @param documentType - Document type being matched
 * @returns The date to use for matching
 */
export function selectTransactionDate(
  transaction: AggregatedTransaction,
  documentType: DocumentType,
): Date {
  switch (documentType) {
    case DocumentType.Invoice:
    case DocumentType.CreditInvoice:
    case DocumentType.Receipt:
    case DocumentType.InvoiceReceipt:
      // For invoices, use event_date
      return transaction.date;

    case DocumentType.Other:
    case DocumentType.Proforma:
    case DocumentType.Unprocessed:
      // For flexible types, use event_date as default
      // (caller should calculate both and use better score)
      return transaction.date;

    default:
      // Fallback to event_date for unknown types
      return transaction.date;
  }
}

/**
 * Score a potential match between transaction charge and document charge
 * Aggregates data from both charges and calculates confidence score
 *
 * @param txCharge - Transaction charge to match
 * @param docCharge - Document charge candidate
 * @param userId - Current user UUID for business extraction
 * @param injector - Optional GraphQL modules injector for provider access (for client matching)
 * @returns Match score with confidence and component breakdown
 * @throws Error if aggregation fails (mixed currencies, multiple businesses, etc.)
 */
export async function scoreMatch(
  txCharge: TransactionCharge,
  docCharge: DocumentCharge,
  userId: string,
  injector: Injector,
): Promise<MatchScore> {
  // Aggregate transaction data
  const aggregatedTransaction = aggregateTransactions(txCharge.transactions);

  // Aggregate document data (includes business extraction and amount normalization)
  const aggregatedDocument = aggregateDocuments(docCharge.documents, userId);

  // // For flexible document types, try both dates and use the better score
  // if (
  //   aggregatedDocument.type === 'OTHER' ||
  //   aggregatedDocument.type === 'PROFORMA' ||
  //   aggregatedDocument.type === 'UNPROCESSED'
  // ) {
  //   // Calculate score with event_date
  //   const scoreWithEventDate = await calculateScoreWithDate(
  //     aggregatedTransaction,
  //     aggregatedDocument,
  //     aggregatedTransaction.date,
  //     docCharge.chargeId,
  //     injector,
  //   );

  //   // Calculate score with debit_date (if available)
  //   if (aggregatedTransaction.debitDate) {
  //     const scoreWithDebitDate = await calculateScoreWithDate(
  //       aggregatedTransaction,
  //       aggregatedDocument,
  //       aggregatedTransaction.debitDate,
  //       docCharge.chargeId,
  //       injector,
  //     );

  //     // Return the better score
  //     return scoreWithEventDate.confidenceScore > scoreWithDebitDate.confidenceScore
  //       ? scoreWithEventDate
  //       : scoreWithDebitDate;
  //   }

  //   return scoreWithEventDate;
  // }

  // For specific document types, use the appropriate date
  const transactionDate = selectTransactionDate(
    aggregatedTransaction,
    aggregatedDocument.type as DocumentType,
  );

  return calculateScoreWithDate(
    aggregatedTransaction,
    aggregatedDocument,
    transactionDate,
    docCharge.chargeId,
    injector,
  );
}

/**
 * Helper function to calculate match score with a specific transaction date
 * @param transaction - Aggregated transaction
 * @param document - Aggregated document
 * @param transactionDate - Date to use from transaction
 * @param chargeId - Document charge ID
 * @param injector - Optional GraphQL modules injector for provider access
 * @returns Match score
 */
async function calculateScoreWithDate(
  transaction: Omit<AggregatedTransaction, 'debitDate'>,
  document: Omit<AggregatedDocument, 'businessIsCreditor'>,
  transactionDate: Date,
  chargeId: string,
  injector: Injector,
): Promise<MatchScore> {
  // Check if transaction and document share the same business entity
  const businessesMatch =
    transaction.businessId != null && transaction.businessId === document.businessId;

  // If businesses match, verify if it's a registered CLIENT
  let isClientMatch = false;
  if (businessesMatch && transaction.businessId) {
    try {
      const client = await injector
        .get(ClientsProvider)
        .getClientByIdLoader.load(transaction.businessId);
      // isClientMatch is true only if business is a registered client
      isClientMatch = client != null;
    } catch (error) {
      console.error(`Error looking up client for business ID ${transaction.businessId}:`, error);
      isClientMatch = false;
    }
  }

  // Determine gentle eligibility for client matches
  // Conditions:
  // - Same business and registered client
  // - Document type is INVOICE or PROFORMA
  // - Document status is OPEN (via issued documents status by charge id)
  // - Document date <= transaction date (date-only compare)
  let isGentleEligible = false;
  if (isClientMatch) {
    // Type gating
    const typeIsEligible =
      document.type === DocumentType.Invoice || document.type === DocumentType.Proforma;

    // Date-only comparison: doc.date <= transactionDate
    const docDate = new Date(
      document.date.getFullYear(),
      document.date.getMonth(),
      document.date.getDate(),
    );
    const txDate = new Date(
      transactionDate.getFullYear(),
      transactionDate.getMonth(),
      transactionDate.getDate(),
    );
    const dateIsEligible = docDate.getTime() <= txDate.getTime();

    // Status gating via DataLoader
    let statusIsEligible: boolean;
    try {
      const status = (await injector
        .get(IssuedDocumentsProvider)
        .getIssuedDocumentsStatusByChargeIdLoader.load(
          chargeId,
        )) as IGetIssuedDocumentsStatusByChargeIdsResult | null;
      // Expect shape with open_docs_flag boolean
      statusIsEligible = !!(status && status.open_docs_flag === true);
    } catch {
      // If status lookup fails, treat as ineligible (do not throw)
      statusIsEligible = false;
    }

    isGentleEligible = typeIsEligible && dateIsEligible && statusIsEligible;
  }

  // Calculate individual confidence scores
  const amountScore = calculateAmountConfidence(transaction.amount, document.amount);
  const currencyScore = calculateCurrencyConfidence(transaction.currency, document.currency);
  const businessScore = calculateBusinessConfidence(transaction.businessId, document.businessId);
  // Use gentle eligibility to compute date confidence
  const dateScore = calculateDateConfidence(transactionDate, document.date, isGentleEligible);

  // Create components object
  const components: ConfidenceScores = {
    amount: amountScore,
    currency: currencyScore,
    business: businessScore,
    date: dateScore,
  };

  // Calculate overall confidence
  const confidenceScore = calculateOverallConfidence(components);

  return {
    chargeId,
    confidenceScore,
    components,
    // Expose gentle flag for tie-breaker handling upstream
    // (optional property tolerated by consumer types)
    gentleMode: isGentleEligible,
  };
}
