import { mkdirSync } from "node:fs";
import { dirname } from "node:path";
import Database from "better-sqlite3";

export type CompanionDatabase = Database.Database;

export function openDatabase(path: string): CompanionDatabase {
  if (path !== ":memory:") mkdirSync(dirname(path), { recursive: true, mode: 0o750 });

  const database = new Database(path);
  database.pragma("journal_mode = WAL");
  database.pragma("foreign_keys = ON");
  database.pragma("busy_timeout = 5000");
  migrate(database);
  return database;
}

function migrate(database: CompanionDatabase): void {
  database.exec(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      username TEXT NOT NULL UNIQUE COLLATE NOCASE,
      password_hash TEXT NOT NULL,
      role TEXT NOT NULL CHECK (role = 'admin'),
      created_at TEXT NOT NULL
    );

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

    CREATE INDEX IF NOT EXISTS sessions_expiry_idx ON sessions(expires_at);

    CREATE TABLE IF NOT EXISTS audit_events (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
      action TEXT NOT NULL,
      result TEXT NOT NULL CHECK (result IN ('success', 'failure')),
      ip_address TEXT,
      details_json TEXT NOT NULL DEFAULT '{}',
      created_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS job_runs (
      id TEXT PRIMARY KEY,
      job_id TEXT NOT NULL,
      actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
      status TEXT NOT NULL CHECK (status IN (
        'queued', 'running', 'succeeded', 'failed', 'timed_out', 'interrupted'
      )),
      input_json TEXT NOT NULL DEFAULT '{}',
      output_json TEXT,
      error_code TEXT,
      created_at TEXT NOT NULL,
      started_at TEXT,
      finished_at TEXT
    );

    CREATE INDEX IF NOT EXISTS job_runs_created_idx ON job_runs(created_at DESC);

    CREATE TABLE IF NOT EXISTS adb_shortcuts (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      target_type TEXT NOT NULL CHECK (target_type IN ('component', 'package')),
      target_value TEXT NOT NULL,
      created_at TEXT NOT NULL,
      UNIQUE(target_type, target_value)
    );

    CREATE TABLE IF NOT EXISTS mobile_pairing_codes (
      id INTEGER PRIMARY KEY CHECK (id = 1),
      code_hash TEXT NOT NULL,
      expires_at TEXT NOT NULL,
      created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
      created_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS mobile_devices (
      id TEXT PRIMARY KEY,
      name TEXT NOT NULL,
      token_hash TEXT NOT NULL UNIQUE,
      created_at TEXT NOT NULL,
      last_seen_at TEXT NOT NULL,
      revoked_at TEXT
    );

    CREATE TABLE IF NOT EXISTS mobile_phone_state (
      device_id TEXT PRIMARY KEY REFERENCES mobile_devices(id) ON DELETE CASCADE,
      received_at TEXT NOT NULL,
      captured_at TEXT NOT NULL,
      latitude REAL NOT NULL,
      longitude REAL NOT NULL,
      accuracy_meters REAL NOT NULL,
      altitude_meters REAL,
      speed_kph REAL,
      bearing_degrees REAL,
      battery_percent REAL,
      network_type TEXT
    );

    CREATE TABLE IF NOT EXISTS trips (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      device_id TEXT NOT NULL REFERENCES mobile_devices(id) ON DELETE CASCADE,
      started_at INTEGER NOT NULL,
      ended_at INTEGER,
      last_moving_at INTEGER NOT NULL,
      start_latitude REAL NOT NULL,
      start_longitude REAL NOT NULL,
      end_latitude REAL NOT NULL,
      end_longitude REAL NOT NULL,
      distance_km REAL NOT NULL DEFAULT 0,
      moving_seconds REAL NOT NULL DEFAULT 0,
      point_count INTEGER NOT NULL DEFAULT 0,
      speed_sum REAL NOT NULL DEFAULT 0,
      speed_count INTEGER NOT NULL DEFAULT 0,
      peak_speed_kph REAL,
      peak_speed_at INTEGER,
      peak_speed_latitude REAL,
      peak_speed_longitude REAL,
      vehicle_battery_start REAL,
      vehicle_battery_end REAL,
      vehicle_consumption_start_kwh REAL,
      vehicle_consumption_end_kwh REAL,
      phone_battery_start REAL,
      phone_battery_end REAL
    );

    CREATE INDEX IF NOT EXISTS trips_started_idx ON trips(started_at DESC);
    CREATE UNIQUE INDEX IF NOT EXISTS trips_active_device_idx ON trips(device_id) WHERE ended_at IS NULL;

    CREATE TABLE IF NOT EXISTS trip_points (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE,
      captured_at INTEGER NOT NULL,
      latitude REAL NOT NULL,
      longitude REAL NOT NULL,
      accuracy_meters REAL NOT NULL,
      altitude_meters REAL,
      speed_kph REAL,
      bearing_degrees REAL,
      vehicle_battery_percent REAL,
      UNIQUE(trip_id, captured_at)
    );

    CREATE INDEX IF NOT EXISTS trip_points_trip_time_idx ON trip_points(trip_id, captured_at);

    CREATE TABLE IF NOT EXISTS car_telemetry_samples (
      recorded_at INTEGER PRIMARY KEY,
      speed_kph REAL,
      wheel_angle_degrees REAL,
      battery_percent REAL,
      range_km REAL,
      battery_voltage REAL,
      total_consumption_kwh REAL,
      charging_status INTEGER,
      charging_power_kw REAL
    ) WITHOUT ROWID;

    CREATE TABLE IF NOT EXISTS car_telemetry_hourly (
      bucket_start INTEGER PRIMARY KEY,
      sample_count INTEGER NOT NULL,
      speed_sum REAL NOT NULL,
      speed_count INTEGER NOT NULL,
      speed_max REAL,
      soc_sum REAL NOT NULL,
      soc_count INTEGER NOT NULL,
      soc_min REAL,
      soc_max REAL,
      range_sum REAL NOT NULL,
      range_count INTEGER NOT NULL,
      range_max REAL,
      charge_power_sum REAL NOT NULL,
      charge_power_count INTEGER NOT NULL,
      charge_power_max REAL,
      distance_km REAL NOT NULL,
      driving_seconds REAL NOT NULL,
      charging_seconds REAL NOT NULL
    ) WITHOUT ROWID;

    CREATE TABLE IF NOT EXISTS car_telemetry_stats (
      id INTEGER PRIMARY KEY CHECK (id = 1),
      first_sample_at INTEGER,
      last_sample_at INTEGER,
      sample_count INTEGER NOT NULL DEFAULT 0,
      peak_speed_kph REAL,
      peak_speed_at INTEGER,
      peak_charging_power_kw REAL,
      peak_charging_power_at INTEGER,
      max_observed_range_km REAL,
      max_observed_range_at INTEGER,
      tracked_distance_km REAL NOT NULL DEFAULT 0,
      driving_seconds REAL NOT NULL DEFAULT 0,
      charging_seconds REAL NOT NULL DEFAULT 0,
      charge_sessions INTEGER NOT NULL DEFAULT 0,
      soc_used_percent REAL NOT NULL DEFAULT 0
    );

    INSERT OR IGNORE INTO car_telemetry_stats (id) VALUES (1);
  `);

  database
    .prepare(
      `UPDATE job_runs
       SET status = 'interrupted', error_code = 'PROCESS_RESTARTED', finished_at = ?
       WHERE status IN ('queued', 'running')`
    )
    .run(new Date().toISOString());
}
