PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;

CREATE TABLE IF NOT EXISTS categories (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  sort_order INTEGER,
  deleted_at TEXT,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS bills (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  category_id INTEGER REFERENCES categories(id) ON DELETE SET NULL,
  due_day INTEGER NOT NULL CHECK(due_day BETWEEN 1 AND 31),
  override_due_date TEXT,
  bucket TEXT CHECK(bucket IN ('1st', '15th')),
  expected_amount INTEGER NOT NULL DEFAULT 0, -- cents
  interest_rate REAL CHECK(interest_rate IS NULL OR (interest_rate >= 0 AND interest_rate <= 100)),
  billing_cycle TEXT DEFAULT 'monthly' CHECK(billing_cycle IN ('monthly', 'quarterly', 'annually', 'irregular')),
  cycle_type TEXT NOT NULL DEFAULT 'monthly' CHECK(cycle_type IN ('monthly', 'weekly', 'biweekly', 'quarterly', 'annual')),
  cycle_day TEXT,
  autopay_enabled INTEGER NOT NULL DEFAULT 0,
  autodraft_status TEXT NOT NULL DEFAULT 'none' CHECK(autodraft_status IN ('none', 'pending', 'assumed_paid', 'confirmed')),
  auto_mark_paid INTEGER NOT NULL DEFAULT 0,
  website TEXT,
  username TEXT,
  account_info TEXT,
  has_2fa INTEGER NOT NULL DEFAULT 0,
  active INTEGER NOT NULL DEFAULT 1,
  current_balance INTEGER, -- cents
  minimum_payment INTEGER, -- cents
  snowball_order INTEGER,
  sort_order INTEGER,
  snowball_include INTEGER NOT NULL DEFAULT 0,
  snowball_exempt INTEGER NOT NULL DEFAULT 0,
  is_subscription INTEGER NOT NULL DEFAULT 0,
  subscription_type TEXT,
  reminder_days_before INTEGER NOT NULL DEFAULT 3,
  subscription_source TEXT NOT NULL DEFAULT 'manual',
  subscription_detected_at TEXT,
  deleted_at TEXT,
  notes TEXT,
  drift_snoozed_until TEXT,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS payments (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  bill_id INTEGER NOT NULL REFERENCES bills(id) ON DELETE CASCADE,
  amount INTEGER NOT NULL, -- cents
  paid_date TEXT NOT NULL,
  method TEXT,
  notes TEXT,
  balance_delta INTEGER, -- cents
  payment_source TEXT NOT NULL DEFAULT 'manual',
  transaction_id INTEGER,
  accounting_excluded INTEGER NOT NULL DEFAULT 0,
  exclusion_reason TEXT,
  excluded_at TEXT,
  overridden_by_payment_id INTEGER,
  deleted_at TEXT,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS settings (
  key TEXT PRIMARY KEY,
  value TEXT NOT NULL,
  updated_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT NOT NULL UNIQUE COLLATE NOCASE,
  display_name TEXT,
  password_hash TEXT NOT NULL,
  role TEXT NOT NULL DEFAULT 'user' CHECK(role IN ('admin', 'user')),
  active INTEGER NOT NULL DEFAULT 1,
  is_default_admin INTEGER NOT NULL DEFAULT 0,
  must_change_password INTEGER NOT NULL DEFAULT 0,
  first_login INTEGER NOT NULL DEFAULT 1,
  snowball_extra_payment INTEGER NOT NULL DEFAULT 0, -- cents
  notify_amount_change INTEGER NOT NULL DEFAULT 1,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS user_settings (
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  key TEXT NOT NULL,
  value TEXT NOT NULL,
  updated_at TEXT DEFAULT (datetime('now')),
  PRIMARY KEY (user_id, key)
);

CREATE TABLE IF NOT EXISTS data_sources (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  type TEXT NOT NULL,
  provider TEXT,
  name TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'active',
  config_json TEXT,
  encrypted_secret TEXT,
  last_sync_at TEXT,
  last_error TEXT,
  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS financial_accounts (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  data_source_id INTEGER,
  provider_account_id TEXT,
  name TEXT NOT NULL,
  org_name TEXT,
  account_type TEXT,
  currency TEXT,
  balance INTEGER,
  available_balance INTEGER,
  monitored INTEGER NOT NULL DEFAULT 1,
  raw_data TEXT,
  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,

  FOREIGN KEY (data_source_id) REFERENCES data_sources(id) ON DELETE SET NULL,
  UNIQUE(data_source_id, provider_account_id)
);

CREATE TABLE IF NOT EXISTS transactions (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  data_source_id INTEGER,
  account_id INTEGER,
  provider_transaction_id TEXT,
  source_type TEXT NOT NULL,
  transaction_type TEXT,
  posted_date TEXT,
  transacted_at TEXT,
  amount INTEGER NOT NULL,
  currency TEXT,
  description TEXT,
  payee TEXT,
  memo TEXT,
  category TEXT,
  raw_data TEXT,
  matched_bill_id INTEGER,
  match_status TEXT NOT NULL DEFAULT 'unmatched',
  ignored INTEGER NOT NULL DEFAULT 0,
  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,

  FOREIGN KEY (data_source_id) REFERENCES data_sources(id) ON DELETE SET NULL,
  FOREIGN KEY (account_id) REFERENCES financial_accounts(id) ON DELETE SET NULL,
  FOREIGN KEY (matched_bill_id) REFERENCES bills(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS sessions (
  id TEXT PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  expires_at TEXT NOT NULL,
  created_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS user_login_history (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  logged_in_at TEXT NOT NULL DEFAULT (datetime('now')),
  ip_address TEXT,
  user_agent TEXT,
  browser TEXT,
  os TEXT,
  device_type TEXT,
  device_fingerprint TEXT
);

CREATE TABLE IF NOT EXISTS notifications (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,
  bill_id     INTEGER NOT NULL REFERENCES bills(id) ON DELETE CASCADE,
  user_id     INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  year        INTEGER NOT NULL,
  month       INTEGER NOT NULL,
  type        TEXT NOT NULL,   -- due_3d | due_1d | due_today | overdue
  sent_date   TEXT NOT NULL DEFAULT (date('now')),
  UNIQUE(bill_id, user_id, year, month, type, sent_date)
);

CREATE INDEX IF NOT EXISTS idx_notifications_lookup ON notifications(bill_id, user_id, year, month);
CREATE INDEX IF NOT EXISTS idx_bills_active ON bills(active);
CREATE INDEX IF NOT EXISTS idx_payments_bill_id ON payments(bill_id);
CREATE INDEX IF NOT EXISTS idx_payments_paid_date ON payments(paid_date);
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_data_sources_user_type ON data_sources(user_id, type, status);
CREATE UNIQUE INDEX IF NOT EXISTS idx_data_sources_user_manual
  ON data_sources(user_id, type, provider)
  WHERE type = 'manual' AND provider = 'manual';
CREATE INDEX IF NOT EXISTS idx_financial_accounts_user_source ON financial_accounts(user_id, data_source_id);
CREATE INDEX IF NOT EXISTS idx_transactions_user_date ON transactions(user_id, posted_date, transacted_at);
CREATE INDEX IF NOT EXISTS idx_transactions_user_match ON transactions(user_id, match_status, ignored);
CREATE INDEX IF NOT EXISTS idx_transactions_account ON transactions(account_id);
CREATE INDEX IF NOT EXISTS idx_transactions_matched_bill ON transactions(matched_bill_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_provider_dedupe
  ON transactions (data_source_id, provider_transaction_id)
  WHERE provider_transaction_id IS NOT NULL;

CREATE TABLE IF NOT EXISTS match_suggestion_rejections (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  transaction_id INTEGER NOT NULL REFERENCES transactions(id) ON DELETE CASCADE,
  bill_id INTEGER NOT NULL REFERENCES bills(id) ON DELETE CASCADE,
  rejected_at TEXT NOT NULL DEFAULT (datetime('now')),
  UNIQUE(user_id, transaction_id, bill_id)
);

CREATE INDEX IF NOT EXISTS idx_match_suggestion_rejections_user
  ON match_suggestion_rejections(user_id, transaction_id, bill_id);

CREATE TABLE IF NOT EXISTS monthly_bill_state (
  id            INTEGER PRIMARY KEY AUTOINCREMENT,
  bill_id       INTEGER NOT NULL REFERENCES bills(id) ON DELETE CASCADE,
  year          INTEGER NOT NULL CHECK(year BETWEEN 2000 AND 2100),
  month         INTEGER NOT NULL CHECK(month BETWEEN 1 AND 12),
  actual_amount INTEGER,         -- cents; NULL = use bill.expected_amount for this month
  notes         TEXT,           -- month-specific notes, NULL = no notes
  is_skipped    INTEGER NOT NULL DEFAULT 0,  -- 1 = hidden/removed for this month only
  snoozed_until TEXT,           -- ISO date: hide from overdue command center until this date
  created_at    TEXT DEFAULT (datetime('now')),
  updated_at    TEXT DEFAULT (datetime('now')),
  UNIQUE(bill_id, year, month)
);

CREATE INDEX IF NOT EXISTS idx_monthly_bill_state_lookup
  ON monthly_bill_state(bill_id, year, month);

CREATE TABLE IF NOT EXISTS autopay_suggestion_dismissals (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  bill_id INTEGER NOT NULL REFERENCES bills(id) ON DELETE CASCADE,
  year INTEGER NOT NULL CHECK(year BETWEEN 2000 AND 2100),
  month INTEGER NOT NULL CHECK(month BETWEEN 1 AND 12),
  dismissed_at TEXT NOT NULL DEFAULT (datetime('now')),
  UNIQUE(user_id, bill_id, year, month)
);

CREATE INDEX IF NOT EXISTS idx_autopay_suggestion_dismissals_user_month
  ON autopay_suggestion_dismissals(user_id, year, month);

CREATE TABLE IF NOT EXISTS bill_templates (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  data TEXT NOT NULL,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now')),
  UNIQUE(user_id, name)
);

CREATE INDEX IF NOT EXISTS idx_bill_templates_user_name
  ON bill_templates(user_id, name);

CREATE TABLE IF NOT EXISTS advisory_non_bill_filters (
  id TEXT PRIMARY KEY,
  pattern TEXT NOT NULL,
  confidence TEXT NOT NULL CHECK(confidence IN ('high', 'medium')),
  category TEXT NOT NULL,
  rationale TEXT
);

CREATE INDEX IF NOT EXISTS idx_advisory_filters_confidence
  ON advisory_non_bill_filters(confidence);

CREATE TABLE IF NOT EXISTS advisory_bill_like_overrides (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  term TEXT NOT NULL UNIQUE
);

CREATE TABLE IF NOT EXISTS snowball_plans (
  id            INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id       INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  name          TEXT    NOT NULL DEFAULT 'Snowball Plan',
  method        TEXT    NOT NULL DEFAULT 'snowball',
  status        TEXT    NOT NULL DEFAULT 'active',
  started_at    TEXT    NOT NULL DEFAULT (datetime('now')),
  paused_at     TEXT,
  completed_at  TEXT,
  extra_payment INTEGER NOT NULL DEFAULT 0, -- cents
  plan_snapshot TEXT    NOT NULL,
  notes         TEXT,
  created_at    TEXT    NOT NULL DEFAULT (datetime('now')),
  updated_at    TEXT    NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_snowball_plans_user
  ON snowball_plans(user_id, status, created_at);

CREATE TABLE IF NOT EXISTS calendar_tokens (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  token TEXT NOT NULL UNIQUE,
  label TEXT,
  active INTEGER NOT NULL DEFAULT 1,
  last_used_at TEXT,
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  revoked_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_calendar_tokens_token ON calendar_tokens(token) WHERE active = 1;
CREATE INDEX IF NOT EXISTS idx_calendar_tokens_user_active ON calendar_tokens(user_id, active);
