-- 001_init.sql
PRAGMA foreign_keys = ON;

CREATE TABLE IF NOT EXISTS users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT NOT NULL UNIQUE,
  password_salt TEXT NOT NULL,
  password_hash TEXT NOT NULL,
  rating INTEGER NOT NULL DEFAULT 0,
  created_at INTEGER NOT NULL
);

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

CREATE TABLE IF NOT EXISTS user_experience (
  user_id INTEGER PRIMARY KEY,
  xp INTEGER NOT NULL DEFAULT 0,
  updated_at INTEGER NOT NULL,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS user_experience_logs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL,
  xp_delta INTEGER NOT NULL,
  rating_before INTEGER NOT NULL DEFAULT 0,
  rating_after INTEGER NOT NULL DEFAULT 0,
  source TEXT NOT NULL DEFAULT 'users.rating',
  note TEXT NOT NULL DEFAULT '',
  created_at INTEGER NOT NULL,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS problems (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  slug TEXT NOT NULL UNIQUE,
  title TEXT NOT NULL,
  statement_md TEXT NOT NULL,
  difficulty INTEGER NOT NULL DEFAULT 1,
  source TEXT NOT NULL DEFAULT '',
  statement_url TEXT NOT NULL DEFAULT '',
  llm_profile_json TEXT NOT NULL DEFAULT '{}',
  sample_input TEXT NOT NULL DEFAULT '',
  sample_output TEXT NOT NULL DEFAULT '',
  created_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS problem_tags (
  problem_id INTEGER NOT NULL,
  tag TEXT NOT NULL,
  PRIMARY KEY(problem_id, tag),
  FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS submissions (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL,
  problem_id INTEGER NOT NULL,
  contest_id INTEGER,
  language TEXT NOT NULL,
  code TEXT NOT NULL,
  status TEXT NOT NULL,
  score INTEGER NOT NULL DEFAULT 0,
  time_ms INTEGER NOT NULL DEFAULT 0,
  memory_kb INTEGER NOT NULL DEFAULT 0,
  compile_log TEXT NOT NULL DEFAULT '',
  runtime_log TEXT NOT NULL DEFAULT '',
  created_at INTEGER NOT NULL,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
  FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE,
  FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS wrong_book (
  user_id INTEGER NOT NULL,
  problem_id INTEGER NOT NULL,
  last_submission_id INTEGER,
  note TEXT NOT NULL DEFAULT '',
  updated_at INTEGER NOT NULL,
  PRIMARY KEY(user_id, problem_id),
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
  FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE,
  FOREIGN KEY(last_submission_id) REFERENCES submissions(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS contests (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  starts_at INTEGER NOT NULL,
  ends_at INTEGER NOT NULL,
  rule_json TEXT NOT NULL DEFAULT '{}'
);

CREATE TABLE IF NOT EXISTS contest_problems (
  contest_id INTEGER NOT NULL,
  problem_id INTEGER NOT NULL,
  idx INTEGER NOT NULL,
  PRIMARY KEY(contest_id, problem_id),
  FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE CASCADE,
  FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS contest_registrations (
  contest_id INTEGER NOT NULL,
  user_id INTEGER NOT NULL,
  registered_at INTEGER NOT NULL,
  PRIMARY KEY(contest_id, user_id),
  FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE CASCADE,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS contest_modifiers (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  contest_id INTEGER NOT NULL,
  code TEXT NOT NULL,
  title TEXT NOT NULL,
  description TEXT NOT NULL DEFAULT '',
  rule_json TEXT NOT NULL DEFAULT '{}',
  is_active INTEGER NOT NULL DEFAULT 1,
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL,
  FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE CASCADE,
  UNIQUE(contest_id, code)
);

CREATE TABLE IF NOT EXISTS seasons (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  key TEXT NOT NULL UNIQUE,
  title TEXT NOT NULL,
  starts_at INTEGER NOT NULL,
  ends_at INTEGER NOT NULL,
  status TEXT NOT NULL DEFAULT 'draft',
  pass_json TEXT NOT NULL DEFAULT '{}',
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS season_reward_tracks (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  season_id INTEGER NOT NULL,
  tier_no INTEGER NOT NULL,
  required_xp INTEGER NOT NULL DEFAULT 0,
  reward_type TEXT NOT NULL DEFAULT 'free',
  reward_value INTEGER NOT NULL DEFAULT 0,
  reward_meta_json TEXT NOT NULL DEFAULT '{}',
  FOREIGN KEY(season_id) REFERENCES seasons(id) ON DELETE CASCADE,
  UNIQUE(season_id, tier_no, reward_type)
);

CREATE TABLE IF NOT EXISTS season_user_progress (
  season_id INTEGER NOT NULL,
  user_id INTEGER NOT NULL,
  xp INTEGER NOT NULL DEFAULT 0,
  level INTEGER NOT NULL DEFAULT 0,
  updated_at INTEGER NOT NULL,
  PRIMARY KEY(season_id, user_id),
  FOREIGN KEY(season_id) REFERENCES seasons(id) ON DELETE CASCADE,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS season_reward_claims (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  season_id INTEGER NOT NULL,
  user_id INTEGER NOT NULL,
  tier_no INTEGER NOT NULL,
  reward_type TEXT NOT NULL DEFAULT 'free',
  claimed_at INTEGER NOT NULL,
  FOREIGN KEY(season_id) REFERENCES seasons(id) ON DELETE CASCADE,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
  UNIQUE(season_id, user_id, tier_no, reward_type)
);

CREATE TABLE IF NOT EXISTS loot_drop_logs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL,
  source_type TEXT NOT NULL,
  source_id INTEGER NOT NULL DEFAULT 0,
  item_code TEXT NOT NULL,
  item_name TEXT NOT NULL DEFAULT '',
  rarity TEXT NOT NULL DEFAULT 'common',
  amount INTEGER NOT NULL DEFAULT 0,
  meta_json TEXT NOT NULL DEFAULT '{}',
  created_at INTEGER NOT NULL,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS kb_articles (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  slug TEXT NOT NULL UNIQUE,
  title TEXT NOT NULL,
  content_md TEXT NOT NULL,
  created_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS kb_article_links (
  article_id INTEGER NOT NULL,
  problem_id INTEGER NOT NULL,
  PRIMARY KEY(article_id, problem_id),
  FOREIGN KEY(article_id) REFERENCES kb_articles(id) ON DELETE CASCADE,
  FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS kb_knowledge_claims (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL,
  article_id INTEGER NOT NULL,
  knowledge_key TEXT NOT NULL,
  reward INTEGER NOT NULL DEFAULT 1,
  created_at INTEGER NOT NULL,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
  FOREIGN KEY(article_id) REFERENCES kb_articles(id) ON DELETE CASCADE,
  UNIQUE(user_id, article_id, knowledge_key)
);

CREATE TABLE IF NOT EXISTS kb_weekly_tasks (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL,
  week_key TEXT NOT NULL,
  article_id INTEGER NOT NULL,
  article_slug TEXT NOT NULL,
  article_title TEXT NOT NULL,
  knowledge_key TEXT NOT NULL,
  knowledge_title TEXT NOT NULL,
  knowledge_description TEXT NOT NULL DEFAULT '',
  difficulty TEXT NOT NULL DEFAULT 'bronze',
  reward INTEGER NOT NULL DEFAULT 1,
  prerequisites TEXT NOT NULL DEFAULT '',
  order_no INTEGER NOT NULL DEFAULT 0,
  created_at INTEGER NOT NULL,
  completed_at INTEGER,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
  FOREIGN KEY(article_id) REFERENCES kb_articles(id) ON DELETE CASCADE,
  UNIQUE(user_id, week_key, article_id, knowledge_key)
);

CREATE TABLE IF NOT EXISTS kb_weekly_bonus_logs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL,
  week_key TEXT NOT NULL,
  reward INTEGER NOT NULL DEFAULT 100,
  created_at INTEGER NOT NULL,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
  UNIQUE(user_id, week_key)
);

CREATE TABLE IF NOT EXISTS import_jobs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  status TEXT NOT NULL,
  trigger TEXT NOT NULL DEFAULT 'manual',
  total_count INTEGER NOT NULL DEFAULT 0,
  processed_count INTEGER NOT NULL DEFAULT 0,
  success_count INTEGER NOT NULL DEFAULT 0,
  failed_count INTEGER NOT NULL DEFAULT 0,
  options_json TEXT NOT NULL DEFAULT '{}',
  last_error TEXT NOT NULL DEFAULT '',
  started_at INTEGER NOT NULL,
  finished_at INTEGER,
  updated_at INTEGER NOT NULL,
  created_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS import_job_items (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  job_id INTEGER NOT NULL,
  source_path TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'queued',
  title TEXT NOT NULL DEFAULT '',
  difficulty INTEGER NOT NULL DEFAULT 0,
  problem_id INTEGER,
  error_text TEXT NOT NULL DEFAULT '',
  started_at INTEGER,
  finished_at INTEGER,
  updated_at INTEGER NOT NULL,
  created_at INTEGER NOT NULL,
  FOREIGN KEY(job_id) REFERENCES import_jobs(id) ON DELETE CASCADE,
  UNIQUE(job_id, source_path)
);

CREATE TABLE IF NOT EXISTS crawler_targets (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  url TEXT NOT NULL,
  normalized_url TEXT NOT NULL UNIQUE,
  status TEXT NOT NULL DEFAULT 'queued',
  submit_source TEXT NOT NULL DEFAULT 'manual',
  submitter_id TEXT NOT NULL DEFAULT '',
  submitter_name TEXT NOT NULL DEFAULT '',
  rule_json TEXT NOT NULL DEFAULT '{}',
  script_path TEXT NOT NULL DEFAULT '',
  last_error TEXT NOT NULL DEFAULT '',
  last_test_at INTEGER,
  last_run_at INTEGER,
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS crawler_runs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  target_id INTEGER NOT NULL,
  status TEXT NOT NULL DEFAULT 'queued',
  http_status INTEGER NOT NULL DEFAULT 0,
  output_json TEXT NOT NULL DEFAULT '{}',
  error_text TEXT NOT NULL DEFAULT '',
  created_at INTEGER NOT NULL,
  FOREIGN KEY(target_id) REFERENCES crawler_targets(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS problem_drafts (
  user_id INTEGER NOT NULL,
  problem_id INTEGER NOT NULL,
  language TEXT NOT NULL DEFAULT 'cpp',
  code TEXT NOT NULL DEFAULT '',
  stdin TEXT NOT NULL DEFAULT '',
  updated_at INTEGER NOT NULL,
  created_at INTEGER NOT NULL,
  PRIMARY KEY(user_id, problem_id),
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
  FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS problem_solution_jobs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  problem_id INTEGER NOT NULL,
  status TEXT NOT NULL DEFAULT 'queued',
  progress INTEGER NOT NULL DEFAULT 0,
  message TEXT NOT NULL DEFAULT '',
  created_by INTEGER NOT NULL DEFAULT 0,
  max_solutions INTEGER NOT NULL DEFAULT 3,
  created_at INTEGER NOT NULL,
  started_at INTEGER,
  finished_at INTEGER,
  updated_at INTEGER NOT NULL,
  FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS problem_solutions (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  problem_id INTEGER NOT NULL,
  variant INTEGER NOT NULL DEFAULT 1,
  title TEXT NOT NULL DEFAULT '',
  idea_md TEXT NOT NULL DEFAULT '',
  explanation_md TEXT NOT NULL DEFAULT '',
  code_cpp TEXT NOT NULL DEFAULT '',
  complexity TEXT NOT NULL DEFAULT '',
  tags_json TEXT NOT NULL DEFAULT '[]',
  source TEXT NOT NULL DEFAULT 'llm',
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL,
  FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS source_crystal_settings (
  id INTEGER PRIMARY KEY CHECK(id = 1),
  monthly_interest_rate REAL NOT NULL DEFAULT 0.02,
  updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS source_crystal_accounts (
  user_id INTEGER PRIMARY KEY,
  balance REAL NOT NULL DEFAULT 0,
  last_interest_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS source_crystal_transactions (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL,
  tx_type TEXT NOT NULL,
  amount REAL NOT NULL,
  balance_after REAL NOT NULL,
  note TEXT NOT NULL DEFAULT '',
  created_at INTEGER NOT NULL,
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS user_achievement_milestones (
  user_id INTEGER NOT NULL,
  milestone_no INTEGER NOT NULL,
  completed_count_snapshot INTEGER NOT NULL DEFAULT 0,
  rating_bonus INTEGER NOT NULL DEFAULT 20,
  created_at INTEGER NOT NULL,
  PRIMARY KEY(user_id, milestone_no),
  FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);

INSERT OR IGNORE INTO source_crystal_settings(id, monthly_interest_rate, updated_at)
VALUES(1, 0.02, strftime('%s','now'));

INSERT OR IGNORE INTO user_experience(user_id, xp, updated_at)
SELECT id, CASE WHEN rating > 0 THEN rating ELSE 0 END, strftime('%s','now')
FROM users;

CREATE TRIGGER IF NOT EXISTS trg_users_init_experience
AFTER INSERT ON users
BEGIN
  INSERT OR IGNORE INTO user_experience(user_id, xp, updated_at)
  VALUES(NEW.id, CASE WHEN NEW.rating > 0 THEN NEW.rating ELSE 0 END, strftime('%s','now'));
  INSERT INTO user_experience_logs(user_id, xp_delta, rating_before, rating_after, source, note, created_at)
  SELECT NEW.id,
         NEW.rating,
         0,
         NEW.rating,
         'users.insert',
         'initial rating gain',
         strftime('%s','now')
  WHERE NEW.rating > 0;
END;

CREATE TRIGGER IF NOT EXISTS trg_users_rating_gain_to_experience
AFTER UPDATE OF rating ON users
WHEN NEW.rating > OLD.rating
BEGIN
  INSERT OR IGNORE INTO user_experience(user_id, xp, updated_at)
  VALUES(NEW.id, 0, strftime('%s','now'));
  UPDATE user_experience
  SET xp = xp + (NEW.rating - OLD.rating),
      updated_at = strftime('%s','now')
  WHERE user_id = NEW.id;
  INSERT INTO user_experience_logs(user_id, xp_delta, rating_before, rating_after, source, note, created_at)
  VALUES(NEW.id,
         (NEW.rating - OLD.rating),
         OLD.rating,
         NEW.rating,
         'users.rating',
         'rating gain',
         strftime('%s','now'));
END;

CREATE INDEX IF NOT EXISTS idx_submissions_user_created_at ON submissions(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_submissions_problem_created_at ON submissions(problem_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_submissions_contest_user_created_at ON submissions(contest_id, user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_problem_tags_tag ON problem_tags(tag);
CREATE INDEX IF NOT EXISTS idx_contest_modifiers_contest_active ON contest_modifiers(contest_id, is_active, id);
CREATE INDEX IF NOT EXISTS idx_seasons_status_range ON seasons(status, starts_at, ends_at);
CREATE INDEX IF NOT EXISTS idx_season_reward_tracks_season_tier ON season_reward_tracks(season_id, tier_no, required_xp);
CREATE INDEX IF NOT EXISTS idx_season_user_progress_user ON season_user_progress(user_id, season_id, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_season_reward_claims_user ON season_reward_claims(user_id, season_id, claimed_at DESC);
CREATE INDEX IF NOT EXISTS idx_loot_drop_logs_user_created ON loot_drop_logs(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_kb_article_links_problem_id ON kb_article_links(problem_id);
CREATE INDEX IF NOT EXISTS idx_kb_knowledge_claims_user_article ON kb_knowledge_claims(user_id, article_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_kb_weekly_tasks_user_week_order ON kb_weekly_tasks(user_id, week_key, order_no, id);
CREATE INDEX IF NOT EXISTS idx_kb_weekly_tasks_user_week_completed ON kb_weekly_tasks(user_id, week_key, completed_at);
CREATE INDEX IF NOT EXISTS idx_kb_weekly_bonus_logs_user_week ON kb_weekly_bonus_logs(user_id, week_key);
CREATE INDEX IF NOT EXISTS idx_import_jobs_created_at ON import_jobs(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_import_jobs_status ON import_jobs(status, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_import_job_items_job_status ON import_job_items(job_id, status, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_crawler_targets_status_updated ON crawler_targets(status, updated_at ASC);
CREATE INDEX IF NOT EXISTS idx_crawler_runs_target_created ON crawler_runs(target_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_problem_drafts_updated ON problem_drafts(updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_problem_solution_jobs_problem ON problem_solution_jobs(problem_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_problem_solutions_problem ON problem_solutions(problem_id, variant, id);
CREATE INDEX IF NOT EXISTS idx_user_experience_logs_user_created ON user_experience_logs(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_source_crystal_tx_user_created ON source_crystal_transactions(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_user_achievement_milestones_user_created ON user_achievement_milestones(user_id, created_at DESC);
