// Fiscal Year Utilities for Danish Accounting (Regnskabsår)

import dayjs from 'dayjs';
import type {
  FiscalYear,
  AccountingPeriod,
  ClosingEntryType,
} from '@/types/periods';
import type { Account, Transaction } from '@/types/accounting';

// =====================================================
// FISCAL YEAR BOUNDARIES
// =====================================================

/**
 * Calculate fiscal year boundaries for a given date
 * Based on company's fiscal year start month
 */
export function getFiscalYearBoundaries(
  date: string,
  fiscalYearStartMonth: number
): { startDate: string; endDate: string; name: string } {
  const d = dayjs(date);
  const year = d.year();
  const month = d.month() + 1; // dayjs months are 0-indexed

  let startYear: number;
  let endYear: number;

  if (fiscalYearStartMonth === 1) {
    // Calendar year (January start)
    startYear = year;
    endYear = year;
  } else {
    // Non-calendar fiscal year (e.g., July start)
    if (month >= fiscalYearStartMonth) {
      // We're in the first part of a fiscal year that spans two calendar years
      startYear = year;
      endYear = year + 1;
    } else {
      // We're in the second part of a fiscal year
      startYear = year - 1;
      endYear = year;
    }
  }

  const startDate = dayjs()
    .year(startYear)
    .month(fiscalYearStartMonth - 1)
    .date(1)
    .format('YYYY-MM-DD');

  const endDate = dayjs()
    .year(endYear)
    .month(fiscalYearStartMonth - 1)
    .subtract(1, 'day')
    .endOf('month')
    .format('YYYY-MM-DD');

  // Name format: "2025" for calendar year, "2024/2025" for split year
  const name = startYear === endYear ? `${startYear}` : `${startYear}/${endYear}`;

  return { startDate, endDate, name };
}

/**
 * Check if a date falls within a fiscal year
 */
export function isDateInFiscalYear(date: string, fiscalYear: FiscalYear): boolean {
  const d = dayjs(date);
  const start = dayjs(fiscalYear.startDate);
  const end = dayjs(fiscalYear.endDate);

  return (d.isAfter(start) || d.isSame(start, 'day')) &&
         (d.isBefore(end) || d.isSame(end, 'day'));
}

/**
 * Find the fiscal year that contains a given date
 */
export function findFiscalYearForDate(
  date: string,
  fiscalYears: FiscalYear[]
): FiscalYear | undefined {
  return fiscalYears.find((fy) => isDateInFiscalYear(date, fy));
}

/**
 * Check if a date is outside all existing fiscal years
 */
export function isDateOutsideFiscalYears(
  date: string,
  fiscalYears: FiscalYear[]
): boolean {
  return !findFiscalYearForDate(date, fiscalYears);
}

// =====================================================
// FISCAL YEAR CREATION
// =====================================================

/**
 * Generate a new fiscal year object
 */
export function createFiscalYearObject(
  companyId: string,
  startDate: string,
  endDate: string,
  name: string
): Omit<FiscalYear, 'id' | 'createdAt' | 'updatedAt'> {
  return {
    companyId,
    name,
    startDate,
    endDate,
    status: 'open',
    openingBalancePosted: false,
  };
}

/**
 * Generate fiscal year for a specific date
 */
export function generateFiscalYearForDate(
  date: string,
  companyId: string,
  fiscalYearStartMonth: number
): Omit<FiscalYear, 'id' | 'createdAt' | 'updatedAt'> {
  const { startDate, endDate, name } = getFiscalYearBoundaries(date, fiscalYearStartMonth);
  return createFiscalYearObject(companyId, startDate, endDate, name);
}

// =====================================================
// OPENING BALANCE CALCULATION
// =====================================================

/**
 * Account balance at end of fiscal year
 */
export interface AccountClosingBalance {
  accountId: string;
  accountNumber: string;
  accountName: string;
  accountType: Account['type'];
  closingBalance: number;
}

/**
 * Opening balance for new fiscal year
 */
export interface OpeningBalance {
  accountId: string;
  accountNumber: string;
  accountName: string;
  accountType: Account['type'];
  openingBalance: number;
}

/**
 * Calculate closing balances for all accounts in a fiscal year
 */
export function calculateClosingBalances(
  fiscalYear: FiscalYear,
  accounts: Account[],
  transactions: Transaction[]
): AccountClosingBalance[] {
  // Filter transactions to this fiscal year
  const yearTransactions = transactions.filter((tx) => {
    if (tx.isVoided) return false;
    return isDateInFiscalYear(tx.date, fiscalYear);
  });

  // Calculate balance per account
  const balances = new Map<string, number>();

  for (const tx of yearTransactions) {
    for (const line of tx.lines) {
      const current = balances.get(line.accountId) || 0;
      const change = (line.debit || 0) - (line.credit || 0);
      balances.set(line.accountId, current + change);
    }
  }

  // Build closing balance records
  return accounts.map((account) => ({
    accountId: account.id,
    accountNumber: account.accountNumber,
    accountName: account.name,
    accountType: account.type,
    closingBalance: balances.get(account.id) || 0,
  }));
}

/**
 * Calculate opening balances for a new fiscal year
 * Based on previous year's closing balances
 *
 * Rules:
 * - Balance sheet accounts (asset, liability, equity): carry forward
 * - Income statement accounts (revenue, expense, cogs, personnel, financial, extraordinary): zero
 * - Result is transferred to retained earnings (handled separately in closing entries)
 */
export function calculateOpeningBalances(
  closingBalances: AccountClosingBalance[]
): OpeningBalance[] {
  const balanceSheetTypes: Account['type'][] = ['asset', 'liability', 'equity'];

  return closingBalances
    .filter((cb) => balanceSheetTypes.includes(cb.accountType))
    .map((cb) => ({
      accountId: cb.accountId,
      accountNumber: cb.accountNumber,
      accountName: cb.accountName,
      accountType: cb.accountType,
      openingBalance: cb.closingBalance,
    }));
}

// =====================================================
// YEAR-END CLOSING ENTRIES
// =====================================================

/**
 * Closing summary for display before closing
 */
export interface FiscalYearClosingSummary {
  fiscalYearId: string;
  totalRevenue: number;
  totalExpenses: number;
  netResult: number;
  unreconciledCount: number;
  openPeriodsCount: number;
  canClose: boolean;
  warnings: string[];
  errors: string[];
}

/**
 * Calculate closing summary for a fiscal year
 */
export function calculateClosingSummary(
  fiscalYear: FiscalYear,
  periods: AccountingPeriod[],
  closingBalances: AccountClosingBalance[],
  transactions: Transaction[]
): FiscalYearClosingSummary {
  const warnings: string[] = [];
  const errors: string[] = [];

  // Calculate totals from closing balances
  const revenueTypes: Account['type'][] = ['revenue'];
  const expenseTypes: Account['type'][] = ['cogs', 'expense', 'personnel', 'financial', 'extraordinary'];

  // Revenue is typically credit (negative balance in our debit-credit system)
  const totalRevenue = Math.abs(
    closingBalances
      .filter((cb) => revenueTypes.includes(cb.accountType))
      .reduce((sum, cb) => sum + cb.closingBalance, 0)
  );

  // Expenses are typically debit (positive balance)
  const totalExpenses = closingBalances
    .filter((cb) => expenseTypes.includes(cb.accountType))
    .reduce((sum, cb) => sum + cb.closingBalance, 0);

  const netResult = totalRevenue - totalExpenses;

  // Check for unreconciled transactions
  const yearTransactions = transactions.filter(
    (tx) => !tx.isVoided && isDateInFiscalYear(tx.date, fiscalYear)
  );
  const unreconciledCount = yearTransactions.filter((tx) => !tx.isReconciled).length;
  if (unreconciledCount > 0) {
    warnings.push(
      `${unreconciledCount} transaktioner er ikke afstemt`
    );
  }

  // Check for open periods
  const yearPeriods = periods.filter((p) => p.fiscalYearId === fiscalYear.id);
  const openPeriodsCount = yearPeriods.filter((p) => p.status === 'open').length;
  if (openPeriodsCount > 0) {
    warnings.push(
      `${openPeriodsCount} perioder er stadig åbne`
    );
  }

  // Check if fiscal year is already closed/locked
  if (fiscalYear.status === 'locked') {
    errors.push('Regnskabsåret er allerede låst');
  }

  const canClose = errors.length === 0;

  return {
    fiscalYearId: fiscalYear.id,
    totalRevenue,
    totalExpenses,
    netResult,
    unreconciledCount,
    openPeriodsCount,
    canClose,
    warnings,
    errors,
  };
}

/**
 * Generate closing entries for year-end
 */
export interface GeneratedClosingEntry {
  type: ClosingEntryType;
  description: string;
  descriptionDanish: string;
  lines: Array<{
    accountId: string;
    accountNumber: string;
    accountName: string;
    debit: number;
    credit: number;
  }>;
  totalAmount: number;
}

/**
 * Generate year-end closing entries
 */
export function generateClosingEntries(
  fiscalYear: FiscalYear,
  closingBalances: AccountClosingBalance[],
  resultAccountId: string,
  resultAccountNumber: string,
  resultAccountName: string
): GeneratedClosingEntry[] {
  const entries: GeneratedClosingEntry[] = [];

  const revenueTypes: Account['type'][] = ['revenue'];
  const expenseTypes: Account['type'][] = ['cogs', 'expense', 'personnel', 'financial', 'extraordinary'];

  // 1. Close revenue accounts to result account
  const revenueBalances = closingBalances.filter(
    (cb) => revenueTypes.includes(cb.accountType) && cb.closingBalance !== 0
  );

  if (revenueBalances.length > 0) {
    const revenueLines = revenueBalances.map((cb) => ({
      accountId: cb.accountId,
      accountNumber: cb.accountNumber,
      accountName: cb.accountName,
      // Revenue typically has credit balance (negative), so we debit to close
      debit: cb.closingBalance < 0 ? Math.abs(cb.closingBalance) : 0,
      credit: cb.closingBalance > 0 ? cb.closingBalance : 0,
    }));

    const totalRevenue = revenueBalances.reduce((sum, cb) => sum + cb.closingBalance, 0);

    // Counter entry to result account
    revenueLines.push({
      accountId: resultAccountId,
      accountNumber: resultAccountNumber,
      accountName: resultAccountName,
      debit: totalRevenue > 0 ? totalRevenue : 0,
      credit: totalRevenue < 0 ? Math.abs(totalRevenue) : 0,
    });

    entries.push({
      type: 'revenue-close',
      description: `Close revenue accounts for fiscal year ${fiscalYear.name}`,
      descriptionDanish: `Luk indtægtskonti for regnskabsår ${fiscalYear.name}`,
      lines: revenueLines,
      totalAmount: Math.abs(totalRevenue),
    });
  }

  // 2. Close expense accounts to result account
  const expenseBalances = closingBalances.filter(
    (cb) => expenseTypes.includes(cb.accountType) && cb.closingBalance !== 0
  );

  if (expenseBalances.length > 0) {
    const expenseLines = expenseBalances.map((cb) => ({
      accountId: cb.accountId,
      accountNumber: cb.accountNumber,
      accountName: cb.accountName,
      // Expenses typically have debit balance (positive), so we credit to close
      debit: cb.closingBalance < 0 ? Math.abs(cb.closingBalance) : 0,
      credit: cb.closingBalance > 0 ? cb.closingBalance : 0,
    }));

    const totalExpenses = expenseBalances.reduce((sum, cb) => sum + cb.closingBalance, 0);

    // Counter entry to result account
    expenseLines.push({
      accountId: resultAccountId,
      accountNumber: resultAccountNumber,
      accountName: resultAccountName,
      debit: totalExpenses < 0 ? Math.abs(totalExpenses) : 0,
      credit: totalExpenses > 0 ? totalExpenses : 0,
    });

    entries.push({
      type: 'expense-close',
      description: `Close expense accounts for fiscal year ${fiscalYear.name}`,
      descriptionDanish: `Luk udgiftskonti for regnskabsår ${fiscalYear.name}`,
      lines: expenseLines,
      totalAmount: Math.abs(totalExpenses),
    });
  }

  return entries;
}

// =====================================================
// VALIDATION
// =====================================================

/**
 * Validation result for fiscal year operations
 */
export interface FiscalYearValidation {
  isValid: boolean;
  canProceed: boolean;
  errors: Array<{ code: string; message: string; messageDanish: string }>;
  warnings: Array<{ code: string; message: string; messageDanish: string }>;
}

/**
 * Validate if a fiscal year can be closed
 */
export function validateFiscalYearClose(
  fiscalYear: FiscalYear,
  periods: AccountingPeriod[]
): FiscalYearValidation {
  const errors: FiscalYearValidation['errors'] = [];
  const warnings: FiscalYearValidation['warnings'] = [];

  // Check if already locked
  if (fiscalYear.status === 'locked') {
    errors.push({
      code: 'ALREADY_LOCKED',
      message: 'Fiscal year is already locked',
      messageDanish: 'Regnskabsåret er allerede låst',
    });
  }

  // Check for open periods
  const yearPeriods = periods.filter((p) => p.fiscalYearId === fiscalYear.id);
  const openPeriods = yearPeriods.filter((p) => p.status === 'open');

  if (openPeriods.length > 0) {
    warnings.push({
      code: 'OPEN_PERIODS',
      message: `${openPeriods.length} accounting period(s) are still open`,
      messageDanish: `${openPeriods.length} regnskabsperiode(r) er stadig åbne`,
    });
  }

  // Check for future periods
  const futurePeriods = yearPeriods.filter((p) => p.status === 'future');
  if (futurePeriods.length > 0) {
    warnings.push({
      code: 'FUTURE_PERIODS',
      message: `${futurePeriods.length} accounting period(s) have not started yet`,
      messageDanish: `${futurePeriods.length} regnskabsperiode(r) er ikke startet endnu`,
    });
  }

  return {
    isValid: errors.length === 0,
    canProceed: errors.length === 0,
    errors,
    warnings,
  };
}

/**
 * Validate if a new fiscal year can be created
 */
export function validateFiscalYearCreation(
  newYear: Pick<FiscalYear, 'startDate' | 'endDate'>,
  existingYears: FiscalYear[]
): FiscalYearValidation {
  const errors: FiscalYearValidation['errors'] = [];
  const warnings: FiscalYearValidation['warnings'] = [];

  const newStart = dayjs(newYear.startDate);
  const newEnd = dayjs(newYear.endDate);

  // Check for overlapping fiscal years
  for (const existing of existingYears) {
    const existingStart = dayjs(existing.startDate);
    const existingEnd = dayjs(existing.endDate);

    // Use strict inequality - two fiscal years that touch at the boundary
    // (one ends on date X, other starts on date X) are allowed
    const overlaps = newStart.isBefore(existingEnd) && newEnd.isAfter(existingStart);

    if (overlaps) {
      errors.push({
        code: 'OVERLAP',
        message: `Overlaps with existing fiscal year ${existing.name}`,
        messageDanish: `Overlapper med eksisterende regnskabsår ${existing.name}`,
      });
    }
  }

  // Check if fiscal year length is reasonable (300-400 days typically)
  const daysDiff = newEnd.diff(newStart, 'day');
  if (daysDiff < 300) {
    warnings.push({
      code: 'SHORT_YEAR',
      message: `Fiscal year is only ${daysDiff} days`,
      messageDanish: `Regnskabsåret er kun ${daysDiff} dage`,
    });
  }
  if (daysDiff > 400) {
    warnings.push({
      code: 'LONG_YEAR',
      message: `Fiscal year is ${daysDiff} days (typically should be ~365)`,
      messageDanish: `Regnskabsåret er ${daysDiff} dage (normalt omkring 365)`,
    });
  }

  return {
    isValid: errors.length === 0,
    canProceed: errors.length === 0,
    errors,
    warnings,
  };
}

// =====================================================
// HELPERS
// =====================================================

/**
 * Get previous fiscal year
 */
export function getPreviousFiscalYear(
  fiscalYear: FiscalYear,
  allYears: FiscalYear[]
): FiscalYear | undefined {
  const sorted = [...allYears].sort(
    (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
  );

  const currentIndex = sorted.findIndex((y) => y.id === fiscalYear.id);
  if (currentIndex === -1 || currentIndex === sorted.length - 1) {
    return undefined;
  }

  return sorted[currentIndex + 1];
}

/**
 * Get next fiscal year
 */
export function getNextFiscalYear(
  fiscalYear: FiscalYear,
  allYears: FiscalYear[]
): FiscalYear | undefined {
  const sorted = [...allYears].sort(
    (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
  );

  const currentIndex = sorted.findIndex((y) => y.id === fiscalYear.id);
  if (currentIndex === -1 || currentIndex === 0) {
    return undefined;
  }

  return sorted[currentIndex - 1];
}

/**
 * Format fiscal year for display
 */
export function formatFiscalYear(fiscalYear: FiscalYear): string {
  return `Regnskabsår ${fiscalYear.name}`;
}

/**
 * Get fiscal year status color
 */
export function getFiscalYearStatusColor(status: FiscalYear['status']): string {
  switch (status) {
    case 'open':
      return 'green';
    case 'closed':
      return 'orange';
    case 'locked':
      return 'red';
    default:
      return 'default';
  }
}

/**
 * Get fiscal year status label in Danish
 */
export function getFiscalYearStatusLabel(status: FiscalYear['status']): string {
  switch (status) {
    case 'open':
      return 'Åben';
    case 'closed':
      return 'Lukket';
    case 'locked':
      return 'Låst';
    default:
      return status;
  }
}
