import { prisma } from '@/lib/prisma';
import { getClientIp } from '@/lib/rateLimit';
import { headers } from 'next/headers';

export interface BankTransactionLine {
  date: Date;
  description: string;
  amount: number; // cents
  balance?: number; // cents
  reference?: string;
}

/**
 * Parse CSV bank statement into structured transactions
 * Expected format: Date,Description,Amount,Balance,Reference
 * or similar variations
 */
export function parseCSV(csvContent: string): BankTransactionLine[] {
  const lines = csvContent.trim().split('\n');
  if (lines.length < 2) return [];

  const transactions: BankTransactionLine[] = [];
  const headerLine = lines[0].toLowerCase();

  // Detect column positions (case-insensitive)
  const dateIdx = detectColumn(headerLine, ['date', 'transaction date', 'date of transaction']);
  const descIdx = detectColumn(headerLine, ['description', 'particulars', 'memo']);
  const amountIdx = detectColumn(headerLine, ['amount', 'debit', 'credit', 'transaction']);
  const balanceIdx = detectColumn(headerLine, ['balance', 'closing balance', 'running balance']);
  const refIdx = detectColumn(headerLine, ['reference', 'ref', 'transaction ref']);

  // Parse data rows
  for (let i = 1; i < lines.length; i++) {
    const row = parseCSVRow(lines[i]);
    if (row.length === 0) continue;

    try {
      const dateStr = row[dateIdx]?.trim();
      const description = row[descIdx]?.trim() || '';
      const amountStr = row[amountIdx]?.trim() || '0';
      const balanceStr = row[balanceIdx]?.trim();
      const reference = row[refIdx]?.trim();

      if (!dateStr || !description) continue;

      const date = parseDate(dateStr);
      if (!date) continue;

      // Parse amount - could be "1,234.56" or "-1234.56" or "(1234.56)" for negative
      let amount = 0;
      const cleanAmount = amountStr.replace(/[()]/g, '').replace(/,/g, '');
      const parsedAmount = parseFloat(cleanAmount) * 100;
      if (!isNaN(parsedAmount)) {
        amount = Math.round(parsedAmount);
        if (amountStr.includes('(')) amount = -amount; // parentheses = negative
      }

      const balance = balanceStr ? Math.round(parseFloat(balanceStr.replace(/,/g, '')) * 100) : undefined;

      transactions.push({
        date,
        description,
        amount,
        balance,
        reference,
      });
    } catch {
      // Skip malformed rows
    }
  }

  return transactions;
}

function detectColumn(headerLine: string, keywords: string[]): number {
  const cols = headerLine.split(',');
  for (const keyword of keywords) {
    for (let i = 0; i < cols.length; i++) {
      if (cols[i].includes(keyword)) return i;
    }
  }
  // Default to column 0 for date, column 1 for others
  return keywords[0] === 'date' ? 0 : 1;
}

function parseCSVRow(line: string): string[] {
  const result: string[] = [];
  let current = '';
  let inQuotes = false;

  for (let i = 0; i < line.length; i++) {
    const char = line[i];
    if (char === '"') {
      inQuotes = !inQuotes;
    } else if (char === ',' && !inQuotes) {
      result.push(current);
      current = '';
    } else {
      current += char;
    }
  }
  result.push(current);
  return result;
}

function parseDate(dateStr: string): Date | null {
  // Try multiple formats: DD/MM/YYYY, YYYY-MM-DD, MM/DD/YYYY
  const formats = [
    /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/, // DD/MM/YYYY or MM/DD/YYYY
    /^(\d{4})-(\d{1,2})-(\d{1,2})$/, // YYYY-MM-DD
    /^(\d{1,2})-(\d{1,2})-(\d{4})$/, // DD-MM-YYYY
  ];

  for (const format of formats) {
    const match = dateStr.match(format);
    if (match) {
      let year, month, day;
      if (format === formats[1]) {
        [, year, month, day] = match;
      } else if (format === formats[2]) {
        [, day, month, year] = match;
      } else {
        // For DD/MM/YYYY format, we need to guess based on value
        const [, first, second] = match;
        const firstNum = parseInt(first);
        const secondNum = parseInt(second);
        if (firstNum > 12) {
          [, day, month] = match; // first must be day
        } else if (secondNum > 12) {
          [, month, day] = match; // second must be day
        } else {
          // Ambiguous - assume DD/MM/YYYY (common in UK)
          [, day, month] = match;
        }
        [, , , year] = match;
      }

      const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
      if (!isNaN(date.getTime())) return date;
    }
  }

  return null;
}

/**
 * Auto-match bank transactions to system transactions
 * Uses fuzzy matching on amount + date proximity
 */
export async function autoMatchTransactions(statementId: string) {
  const statement = await prisma.bankStatement.findUnique({
    where: { id: statementId },
    include: { lines: { where: { matched: false } } },
  });

  if (!statement) throw new Error('Statement not found');

  // Get all unmatched system transactions around the statement date
  const startDate = new Date(statement.startDate);
  startDate.setDate(startDate.getDate() - 7); // Look back 7 days
  const endDate = new Date(statement.endDate);
  endDate.setDate(endDate.getDate() + 7); // Look ahead 7 days

  const orders = await prisma.order.findMany({
    where: { createdAt: { gte: startDate, lte: endDate }, total: { gt: 0 } },
    select: { id: true, total: true, createdAt: true },
  });

  const manualSales = await prisma.manualSale.findMany({
    where: { soldAt: { gte: startDate, lte: endDate }, total: { gt: 0 } },
    select: { id: true, total: true, soldAt: true },
  });

  // Try to match each bank line
  const matches: any[] = [];
  for (const line of statement.lines) {
    if (line.matched) continue;

    const bestMatch = findBestMatch(line, orders, manualSales);
    if (bestMatch) {
      matches.push({
        bankLineId: line.id,
        statementId,
        transactionType: bestMatch.type,
        transactionId: bestMatch.id,
        description: bestMatch.description,
        systemAmount: bestMatch.amount,
        bankAmount: line.amount,
        amountMatches: Math.abs(bestMatch.amount - line.amount) < 1,
        matchedBy: null,
        confidence: bestMatch.confidence,
      });
    }
  }

  // Create matches and update bank lines
  for (const match of matches) {
    await prisma.bankReconciliation.create({ data: match });
    await prisma.bankStatementLine.update({
      where: { id: match.bankLineId },
      data: { matched: true },
    });
  }

  return matches.length;
}

interface MatchCandidate {
  id: string;
  type: 'ORDER' | 'MANUAL_SALE';
  amount: number;
  date: Date;
  description: string;
}

function findBestMatch(
  bankLine: { date: Date; amount: number; description: string },
  orders: any[],
  manualSales: any[]
): { id: string; type: string; amount: number; description: string; confidence: number } | null {
  const candidates: MatchCandidate[] = [
    ...orders.map((o) => ({
      id: o.id,
      type: 'ORDER' as const,
      amount: o.total,
      date: o.createdAt,
      description: `Order #${o.id.slice(0, 8)}`,
    })),
    ...manualSales.map((m) => ({
      id: m.id,
      type: 'MANUAL_SALE' as const,
      amount: m.total,
      date: m.soldAt,
      description: `Manual sale #${m.id.slice(0, 8)}`,
    })),
  ];

  let bestMatch = null;
  let bestScore = 0;

  for (const candidate of candidates) {
    const amountDiff = Math.abs(candidate.amount - bankLine.amount);
    const dateDiff = Math.abs(candidate.date.getTime() - bankLine.date.getTime()) / (1000 * 60 * 60 * 24);

    // Scoring: exact amount match = high confidence, within 3 days = good
    if (amountDiff === 0 && dateDiff <= 3) {
      // Perfect match
      return {
        id: candidate.id,
        type: candidate.type,
        amount: candidate.amount,
        description: candidate.description,
        confidence: 100,
      };
    }

    // Fuzzy scoring
    if (amountDiff <= 100 && dateDiff <= 7) {
      // Within £1 and 7 days
      const score = (100 - amountDiff / 10) * (1 - dateDiff / 7);
      if (score > bestScore) {
        bestScore = score;
        bestMatch = {
          id: candidate.id,
          type: candidate.type,
          amount: candidate.amount,
          description: candidate.description,
          confidence: Math.round(Math.max(50, Math.min(99, score))),
        };
      }
    }
  }

  return bestMatch;
}

/**
 * Get reconciliation summary for a statement
 */
export async function getReconciliationSummary(statementId: string) {
  const statement = await prisma.bankStatement.findUnique({
    where: { id: statementId },
    include: {
      lines: true,
      reconciliations: true,
    },
  });

  if (!statement) return null;

  const matched = statement.reconciliations.filter((r) => r.amountMatches);
  const unmatched = statement.lines.filter((l) => !l.matched);
  const discrepancies = statement.reconciliations.filter((r) => !r.amountMatches);

  const totalBankAmount = statement.lines.reduce((sum, l) => sum + l.amount, 0);
  const totalMatchedAmount = matched.reduce((sum, m) => sum + m.systemAmount, 0);

  return {
    total: statement.lines.length,
    matched: matched.length,
    unmatched: unmatched.length,
    discrepancies: discrepancies.length,
    totalBankAmount,
    totalMatchedAmount,
    unmatchedAmount: totalBankAmount - totalMatchedAmount,
    reconciled: unmatched.length === 0 && discrepancies.length === 0,
  };
}
