import { describe, expect, it } from 'vitest';
import { calculateBudgetProgress, getPeriodRange, type Budget } from '../src/domain/finance/budgets';
import { calculateNextDueDate, getDueRecurring, instantiateRecurring, type RecurringTransaction } from '../src/domain/finance/recurring';
import { buildTransactionCurrency, convertCurrency, getEffectiveRate, validateRateCoverage, type ExchangeRate } from '../src/domain/finance/currency';
import { calculateBillingPeriod, calculateStatementAmount, calculateAvailableCredit, type CreditCard } from '../src/domain/finance/creditCards';
import type { Transaction } from '../src/domain/core/types';

const tx = (over: Partial<Transaction>): Transaction => ({
  id: 't1', date: '2026-07-10', flag: '*', postings: [], tags: [], links: [], source: 'mobile.bean', raw: '',
  ...over,
});

describe('budgets', () => {
  const budget: Budget = { id: 'b1', name: '餐饮', amount: '1000', period: 'monthly', categoryAccount: 'Expenses:Food', startDate: '2026-01-01' };
  const transactions = [
    tx({ id: 't1', date: '2026-07-05', postings: [{ account: 'Assets:Alipay', amount: '-100', currency: 'CNY' }, { account: 'Expenses:Food', amount: '100', currency: 'CNY' }] }),
    tx({ id: 't2', date: '2026-07-10', postings: [{ account: 'Assets:Alipay', amount: '-200', currency: 'CNY' }, { account: 'Expenses:Food', amount: '200', currency: 'CNY' }] }),
    tx({ id: 't3', date: '2026-07-15', postings: [{ account: 'Assets:Alipay', amount: '-50', currency: 'CNY' }, { account: 'Expenses:Transport', amount: '50', currency: 'CNY' }] }),
  ];

  it('计算预算进度', () => {
    const progress = calculateBudgetProgress(budget, transactions, '2026-07-15');
    expect(progress.spent).toBe('300');   // 100+200（Food）
    expect(progress.percentage).toBe(30);  // 300/1000
    expect(progress.overBudget).toBe(false);
  });

  it('多入账与退款与总预算测试', () => {
    const testTxs = [
      // 1. 多分录交易：买了食物 100，交通 50，支付宝付了 150
      tx({ id: 't_multi', date: '2026-07-06', postings: [
        { account: 'Assets:Alipay', amount: '-150', currency: 'CNY' },
        { account: 'Expenses:Food', amount: '100', currency: 'CNY' },
        { account: 'Expenses:Transport', amount: '50', currency: 'CNY' }
      ] }),
      // 2. 退款交易：食物退了 10
      tx({ id: 't_refund', date: '2026-07-07', postings: [
        { account: 'Assets:Alipay', amount: '10', currency: 'CNY' },
        { account: 'Expenses:Food', amount: '-10', currency: 'CNY' }
      ] }),
      // 3. 转账交易：微信转支付宝 50（不应计入预算）
      tx({ id: 't_transfer', date: '2026-07-08', postings: [
        { account: 'Assets:WeChat', amount: '-50', currency: 'CNY' },
        { account: 'Assets:Alipay', amount: '50', currency: 'CNY' }
      ] }),
      // 4. 工资收入：5000（不应计入预算）
      tx({ id: 't_salary', date: '2026-07-09', postings: [
        { account: 'Assets:Alipay', amount: '5000', currency: 'CNY' },
        { account: 'Income:Salary', amount: '-5000', currency: 'CNY' }
      ] })
    ];

    // 食物预算进度应该只计算食物分录：100 - 10 = 90
    const foodProgress = calculateBudgetProgress(budget, testTxs, '2026-07-15');
    expect(foodProgress.spent).toBe('90');

    // 总预算进度应该计算所有 Expenses:* 分录：(100 + 50) - 10 = 140
    const totalBudget: Budget = { id: 'b_total', name: '总预算', amount: '1000', period: 'monthly', startDate: '2026-01-01' };
    const totalProgress = calculateBudgetProgress(totalBudget, testTxs, '2026-07-15');
    expect(totalProgress.spent).toBe('140');
  });

  it('超支检测', () => {
    const smallBudget = { ...budget, amount: '200' };
    const progress = calculateBudgetProgress(smallBudget, transactions, '2026-07-15');
    expect(progress.overBudget).toBe(true);
  });

  it('getPeriodRange monthly', () => {
    const range = getPeriodRange('monthly', '2026-07-15');
    expect(range.start).toBe('2026-07-01');
    expect(range.end).toBe('2026-07-31');
  });

  it('getPeriodRange yearly', () => {
    const range = getPeriodRange('yearly', '2026-07-15');
    expect(range.start).toBe('2026-01-01');
    expect(range.end).toBe('2026-12-31');
  });

  // 边界日回归：旧实现 new Date('YYYY-MM-DD') 按 UTC 解析，负时区会把 07-01 算到 06-30
  it('getPeriodRange monthly 边界日（月初）', () => {
    const range = getPeriodRange('monthly', '2026-07-01');
    expect(range.start).toBe('2026-07-01');
    expect(range.end).toBe('2026-07-31');
  });

  it('getPeriodRange weekly 边界日（周三，跨月起止）', () => {
    // 2026-07-01 是周三 → 本周一起 2026-06-29，周日止 2026-07-05
    const range = getPeriodRange('weekly', '2026-07-01');
    expect(range.start).toBe('2026-06-29');
    expect(range.end).toBe('2026-07-05');
  });

  it('getPeriodRange yearly 边界日（元旦）', () => {
    const range = getPeriodRange('yearly', '2026-01-01');
    expect(range.start).toBe('2026-01-01');
    expect(range.end).toBe('2026-12-31');
  });
});

describe('recurring', () => {
  const r: RecurringTransaction = {
    id: 'r1', name: '房租', frequency: 'monthly', interval: 1, nextDueDate: '2026-07-01', enabled: true,
    draft: { date: '2026-07-01', narration: '房租', postings: [{ account: 'Assets:Bank', amount: '-3000', currency: 'CNY' }, { account: 'Expenses:Rent', amount: '3000', currency: 'CNY' }] },
  };

  it('getDueRecurring 检测到期', () => {
    expect(getDueRecurring([r], '2026-07-01')).toHaveLength(1);
    expect(getDueRecurring([r], '2026-06-30')).toHaveLength(0);
  });

  it('calculateNextDueDate monthly', () => {
    expect(calculateNextDueDate('monthly', 1, '2026-07-01')).toBe('2026-08-01');
    expect(calculateNextDueDate('monthly', 1, '2026-07-15')).toBe('2026-08-15');
    expect(calculateNextDueDate('monthly', 1, '2026-01-31')).toBe('2026-02-28');
    expect(calculateNextDueDate('monthly', 2, '2026-01-31')).toBe('2026-03-31');
  });

  it('calculateNextDueDate weekly', () => {
    expect(calculateNextDueDate('weekly', 2, '2026-07-01')).toBe('2026-07-15');
  });

  it('calculateNextDueDate yearly', () => {
    expect(calculateNextDueDate('yearly', 1, '2026-07-15')).toBe('2027-07-15');
    expect(calculateNextDueDate('yearly', 1, '2024-02-29')).toBe('2025-02-28');
    expect(calculateNextDueDate('yearly', 4, '2024-02-29')).toBe('2028-02-29');
  });

  it('instantiateRecurring', () => {
    const draft = instantiateRecurring(r);
    expect(draft.date).toBe('2026-07-01');
  });
});

describe('currency', () => {
  const rates: ExchangeRate[] = [{ from: 'USD', to: 'CNY', rate: '7.2', date: '2026-07-13' }];

  it('convertCurrency', () => {
    expect(convertCurrency('100', 'USD', 'CNY', rates)).toBe('720.00');
  });

  it('同币种不转换', () => {
    expect(convertCurrency('100', 'CNY', 'CNY', rates)).toBe('100');
  });

  it('getEffectiveRate 手动覆盖优先', () => {
    const overrides = [{ from: 'USD', to: 'CNY', rate: '7.0', enabled: true }];
    const rate = getEffectiveRate('USD', 'CNY', rates, overrides);
    expect(rate?.rate).toBe('7.0');
  });

  it('buildTransactionCurrency 同币种', () => {
    const tc = buildTransactionCurrency('100', 'CNY', 'CNY', rates);
    expect(tc.exchangeRate).toBe('1');
  });

  it('buildTransactionCurrency 跨币种', () => {
    const tc = buildTransactionCurrency('100', 'USD', 'CNY', rates);
    expect(tc.exchangeRate).toBe('7.2');
  });

  it('validateRateCoverage', () => {
    const missing = validateRateCoverage('CNY', ['CNY', 'USD', 'EUR'], rates);
    expect(missing).toEqual(['EUR']);
  });
});

describe('creditCards', () => {
  const card: CreditCard = { id: 'c1', name: '招行信用卡', lastFour: '1234', bankName: 'CMB', billingDay: 5, paymentDay: 25, creditLimit: '10000', currency: 'CNY', linkedAccount: 'Liabilities:CreditCard:CMB' };
  const transactions = [
    tx({ id: 't1', date: '2026-07-10', postings: [{ account: 'Liabilities:CreditCard:CMB', amount: '-100', currency: 'CNY' }, { account: 'Expenses:Food', amount: '100', currency: 'CNY' }] }),
    tx({ id: 't2', date: '2026-07-15', postings: [{ account: 'Liabilities:CreditCard:CMB', amount: '-200', currency: 'CNY' }, { account: 'Expenses:Food', amount: '200', currency: 'CNY' }] }),
  ];

  it('calculateBillingPeriod', () => {
    const period = calculateBillingPeriod(card, new Date('2026-07-20'));
    expect(period.periodStart).toBe('2026-06-05');
    expect(period.periodEnd).toBe('2026-07-05');
    expect(period.dueDate).toBe('2026-07-25');
  });

  it('calculateStatementAmount', () => {
    // 7月10日、15日 不在 6/5~7/5 周期内 → 0
    const period = calculateBillingPeriod(card, new Date('2026-07-20'));
    expect(calculateStatementAmount(transactions, card, period)).toBe('0');
    // 用 8/20 周期（7/5~8/5）→ 包含 7/10、7/15
    const period2 = calculateBillingPeriod(card, new Date('2026-08-20'));
    expect(calculateStatementAmount(transactions, card, period2)).toBe('300');
  });

  it('calculateAvailableCredit', () => {
    expect(calculateAvailableCredit(card, '-500')).toBe('9500');
  });
});
