const VALID_VISIBILITY = ['default', 'all', 'ranges', 'none'];

// Helper function to get default cycle day based on cycle type
function getDefaultCycleDay(cycleType) {
  switch (cycleType) {
    case 'monthly':
      return '1'; // 1st of the month
    case 'weekly':
      return 'monday'; // Monday
    case 'biweekly':
      return 'monday'; // Monday
    case 'quarterly':
      return '1'; // 1st of the quarter
    case 'annual':
      return '1'; // 1st of the year
    default:
      return '1';
  }
}

// Validate cycle_day based on cycle_type
function validateCycleDay(cycleType, cycleDay) {
  if (cycleDay === undefined || cycleDay === null) return { value: getDefaultCycleDay(cycleType) };
  const ct = cycleType || 'monthly';
  switch (ct) {
    case 'monthly': {
      const d = Number(cycleDay);
      if (!Number.isInteger(d) || d < 1 || d > 31) return { error: 'monthly cycle_day must be 1-31' };
      return { value: String(d) };
    }
    case 'weekly':
    case 'biweekly': {
      const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
      if (!days.includes(String(cycleDay).toLowerCase())) return { error: 'weekly/biweekly cycle_day must be a valid day name' };
      return { value: String(cycleDay).toLowerCase() };
    }
    case 'quarterly':
    case 'annual':
      return { value: String(cycleDay).slice(0, 50) };
    default:
      return { value: getDefaultCycleDay(ct) };
  }
}

function parseDueDay(value) {
  const day = Number(value);
  if (!Number.isInteger(day) || day < 1 || day > 31) {
    return { error: 'due_day must be an integer between 1 and 31' };
  }
  return { value: day };
}

function parseInterestRate(value) {
  if (value === undefined) return { value: undefined };
  if (value === null) return { value: null };
  if (typeof value === 'string' && value.trim() === '') return { value: null };

  const rate = Number(value);
  if (!Number.isFinite(rate) || rate < 0 || rate > 100) {
    return { error: 'interest_rate must be a number between 0 and 100, or null' };
  }
  return { value: rate };
}

function getValidCycleTypes() {
  return ['monthly', 'weekly', 'biweekly', 'quarterly', 'annual'];
}

/**
 * Validates and normalizes bill data for creation/update.
 * Returns an object with normalized values and any validation errors.
 */
function validateBillData(data, existingBill = null) {
  const errors = [];
  const normalized = {};

  const validCycleTypes = getValidCycleTypes();

  // name is required
  if (!data.name) {
    errors.push({ field: 'name', message: 'name is required' });
  }
  normalized.name = data.name || null;

  // due_day is required
  if (data.due_day === undefined || data.due_day === null) {
    errors.push({ field: 'due_day', message: 'due_day is required' });
  } else {
    const dueResult = parseDueDay(data.due_day);
    if (dueResult.error) {
      errors.push({ field: 'due_day', message: dueResult.error });
    } else {
      normalized.due_day = dueResult.value;
    }
  }

  // category_id validation
  normalized.category_id = data.category_id !== undefined ? (data.category_id || null) : (existingBill?.category_id || null);

  // override_due_date
  normalized.override_due_date = data.override_due_date !== undefined ? (data.override_due_date || null) : (existingBill?.override_due_date || null);

  // expected_amount
  normalized.expected_amount = data.expected_amount !== undefined ? (parseFloat(data.expected_amount) || 0) : (existingBill?.expected_amount || 0);

  // interest_rate
  if (data.interest_rate !== undefined) {
    const parsedInterest = parseInterestRate(data.interest_rate);
    if (parsedInterest.error) {
      errors.push({ field: 'interest_rate', message: parsedInterest.error });
    } else {
      normalized.interest_rate = parsedInterest.value ?? null;
    }
  } else {
    normalized.interest_rate = existingBill?.interest_rate ?? null;
  }

  // billing_cycle
  normalized.billing_cycle = data.billing_cycle !== undefined ? (data.billing_cycle || 'monthly') : (existingBill?.billing_cycle || 'monthly');

  // autopay_enabled
  normalized.autopay_enabled = data.autopay_enabled !== undefined ? (data.autopay_enabled ? 1 : 0) : (existingBill?.autopay_enabled || 0);

  // autodraft_status
  normalized.autodraft_status = data.autodraft_status !== undefined ? (data.autodraft_status || 'none') : (existingBill?.autodraft_status || 'none');

  // website
  normalized.website = data.website !== undefined ? (data.website || null) : (existingBill?.website || null);

  // username
  normalized.username = data.username !== undefined ? (data.username || null) : (existingBill?.username || null);

  // account_info
  normalized.account_info = data.account_info !== undefined ? (data.account_info || null) : (existingBill?.account_info || null);

  // has_2fa
  normalized.has_2fa = data.has_2fa !== undefined ? (data.has_2fa ? 1 : 0) : (existingBill?.has_2fa || 0);

  // notes
  normalized.notes = data.notes !== undefined ? (data.notes || null) : (existingBill?.notes || null);

  // active
  normalized.active = data.active !== undefined ? (data.active ? 1 : 0) : (existingBill?.active || 1);

  // history_visibility
  const nextVisibility = data.history_visibility !== undefined ? data.history_visibility : (existingBill?.history_visibility || 'default');
  if (!VALID_VISIBILITY.includes(nextVisibility)) {
    errors.push({ field: 'history_visibility', message: `history_visibility must be one of: ${VALID_VISIBILITY.join(', ')}` });
  }
  normalized.history_visibility = nextVisibility;

  // cycle_type and cycle_day
  let nextCycleType = (data.cycle_type !== undefined ? data.cycle_type : existingBill?.cycle_type) || 'monthly';
  let nextCycleDay = existingBill?.cycle_day || getDefaultCycleDay(nextCycleType);

  if (data.cycle_type !== undefined) {
    if (!validCycleTypes.includes(data.cycle_type)) {
      errors.push({ field: 'cycle_type', message: `cycle_type must be one of: ${validCycleTypes.join(', ')}` });
    } else {
      nextCycleType = data.cycle_type;
    }
  }

  const cycleDayResult = validateCycleDay(nextCycleType, data.cycle_day !== undefined ? data.cycle_day : nextCycleDay);
  if (cycleDayResult.error) {
    errors.push({ field: 'cycle_day', message: cycleDayResult.error });
  } else {
    nextCycleDay = cycleDayResult.value;
  }
  normalized.cycle_type = nextCycleType;
  normalized.cycle_day = nextCycleDay;

  // Calculate bucket based on due_day
  normalized.bucket = normalized.due_day <= 14 ? '1st' : '15th';

  // current_balance — outstanding debt balance (nullable)
  if (data.current_balance !== undefined) {
    if (data.current_balance === null || data.current_balance === '') {
      normalized.current_balance = null;
    } else {
      const cb = parseFloat(data.current_balance);
      if (!Number.isFinite(cb) || cb < 0) {
        errors.push({ field: 'current_balance', message: 'current_balance must be a non-negative number' });
      } else {
        normalized.current_balance = cb;
      }
    }
  } else {
    normalized.current_balance = existingBill?.current_balance ?? null;
  }

  // minimum_payment — required minimum payment for debt (nullable)
  if (data.minimum_payment !== undefined) {
    if (data.minimum_payment === null || data.minimum_payment === '') {
      normalized.minimum_payment = null;
    } else {
      const mp = parseFloat(data.minimum_payment);
      if (!Number.isFinite(mp) || mp < 0) {
        errors.push({ field: 'minimum_payment', message: 'minimum_payment must be a non-negative number' });
      } else {
        normalized.minimum_payment = mp;
      }
    }
  } else {
    normalized.minimum_payment = existingBill?.minimum_payment ?? null;
  }

  // snowball_order — drag position on snowball page (nullable integer)
  if (data.snowball_order !== undefined) {
    if (data.snowball_order === null || data.snowball_order === '') {
      normalized.snowball_order = null;
    } else {
      const so = parseInt(data.snowball_order, 10);
      if (!Number.isInteger(so) || so < 0) {
        errors.push({ field: 'snowball_order', message: 'snowball_order must be a non-negative integer' });
      } else {
        normalized.snowball_order = so;
      }
    }
  } else {
    normalized.snowball_order = existingBill?.snowball_order ?? null;
  }

  // snowball_include — manual override to force bill onto snowball page
  normalized.snowball_include = data.snowball_include !== undefined
    ? (data.snowball_include ? 1 : 0)
    : (existingBill?.snowball_include ?? 0);

  // snowball_exempt — manual override to hide an auto-detected debt-like bill
  normalized.snowball_exempt = data.snowball_exempt !== undefined
    ? (data.snowball_exempt ? 1 : 0)
    : (existingBill?.snowball_exempt ?? 0);

  return {
    errors,
    normalized: {
      ...normalized,
      name: normalized.name || null,
      due_day: normalized.due_day || null,
    },
  };
}

/**
 * Validates cycle_day for a given cycle_type without requiring the full bill data.
 */
function validateCycleDayOnly(cycleType, cycleDay) {
  return validateCycleDay(cycleType, cycleDay);
}

/**
 * Computes how a payment affects a debt bill's current_balance, accounting for
 * one month of interest accrual.
 *
 * Returns { new_balance, balance_delta } where balance_delta is negative when
 * the balance was reduced (typical case).  Returns null when the bill has no
 * trackable balance.
 */
function computeBalanceDelta(bill, paymentAmount) {
  const bal  = Number(bill.current_balance);
  const rate = Number(bill.interest_rate) || 0;
  const amt  = Number(paymentAmount);

  if (!Number.isFinite(bal) || bal <= 0) return null;
  if (!Number.isFinite(amt) || amt <= 0)  return null;

  const monthlyInterest = bal * (rate / 100 / 12);
  const raw             = bal + monthlyInterest - amt;
  const newBalance      = Math.round(Math.max(0, raw) * 100) / 100;
  const delta           = Math.round((newBalance - bal)  * 100) / 100;

  return { new_balance: newBalance, balance_delta: delta };
}

module.exports = {
  VALID_VISIBILITY,
  getValidCycleTypes,
  getDefaultCycleDay,
  validateCycleDay,
  parseDueDay,
  parseInterestRate,
  validateBillData,
  validateCycleDayOnly,
  computeBalanceDelta,
};
