import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';

const repoRoot = process.cwd();
const exportScript = path.join(repoRoot, 'scripts', 'import-pocketbase', 'src', 'export-sqlite.ts');

function findPython() {
  for (const candidate of [process.env.PYTHON, process.env.PYTHON_BIN, 'python', 'py', 'python3'].filter(Boolean)) {
    const result = spawnSync(candidate, ['--version'], { encoding: 'utf8' });
    if (result.status === 0) return candidate;
  }
  throw new Error('Python 3 is required for pb-sqlite-export-test');
}

function writeFixture(sqliteDir, storageDir) {
  const python = findPython();
  const script = `
import json
import pathlib
import sqlite3

sqlite_dir = pathlib.Path(r'''${sqliteDir}''')
storage_dir = pathlib.Path(r'''${storageDir}''')
data_db = sqlite_dir / 'data.db'
aux_db = sqlite_dir / 'auxiliary.db'

db = sqlite3.connect(data_db)
db.executescript("""
create table "_collections" (
  id text primary key,
  system boolean,
  type text,
  name text,
  fields text,
  indexes text,
  listRule text,
  viewRule text,
  createRule text,
  updateRule text,
  deleteRule text,
  options text,
  created text,
  updated text
);
create table "users" (
  id text primary key,
  created text,
  updated text,
  phone text,
  password text,
  tokenKey text,
  wechatOpenId text,
  wechatUnionId text,
  stats text
);
create table "questions" (
  id text primary key,
  created text,
  updated text,
  content text,
  options text,
  media text
);
create table "_superusers" (
  id text primary key,
  email text,
  password text
);
""")

insert_collection = """
insert into "_collections" (
  id, system, type, name, fields, indexes, listRule, viewRule, createRule, updateRule, deleteRule, options, created, updated
) values (?, ?, ?, ?, ?, '[]', '', '', '', '', '', '{}', '2026-01-01 00:00:00Z', '2026-01-01 00:00:00Z')
"""
db.execute(insert_collection, ('_pb_users_auth_', 0, 'auth', 'users', json.dumps([
  {'name': 'id', 'type': 'text'},
  {'name': 'password', 'type': 'password'},
  {'name': 'tokenKey', 'type': 'text'},
  {'name': 'phone', 'type': 'text'},
  {'name': 'wechatOpenId', 'type': 'text'},
  {'name': 'wechatUnionId', 'type': 'text'},
  {'name': 'stats', 'type': 'json'},
])))
db.execute(insert_collection, ('pbc_questions', 0, 'base', 'questions', json.dumps([
  {'name': 'id', 'type': 'text'},
  {'name': 'content', 'type': 'text'},
  {'name': 'options', 'type': 'json'},
  {'name': 'media', 'type': 'file', 'maxSelect': 1},
])))
db.execute(insert_collection, ('pbc_superusers', 1, 'auth', '_superusers', json.dumps([
  {'name': 'password', 'type': 'password'},
])))
db.execute(
  'insert into users (id, created, updated, phone, password, tokenKey, wechatOpenId, wechatUnionId, stats) values (?, ?, ?, ?, ?, ?, ?, ?, ?)',
  ('u1', '2026-01-01 00:00:00Z', '2026-01-01 00:00:00Z', '13800000000', 'hash-secret', 'token-secret', 'wx-openid', 'wx-unionid', json.dumps({'answered': 3})),
)
db.execute(
  'insert into questions (id, created, updated, content, options, media) values (?, ?, ?, ?, ?, ?)',
  ('q1', '2026-01-01 00:00:00Z', '2026-01-01 00:00:00Z', '题干', json.dumps(['A', 'B']), 'question.png'),
)
db.execute('insert into _superusers (id, email, password) values (?, ?, ?)', ('su1', 'root@example.com', 'secret'))
db.commit()
db.close()

aux = sqlite3.connect(aux_db)
aux.executescript("""
create table "_logs" (
  id text primary key,
  level integer not null,
  message text not null,
  data text not null,
  created text not null
);
""")
aux.execute(
  'insert into "_logs" (id, level, message, data, created) values (?, ?, ?, ?, ?)',
  ('l1', 1, 'GET /api/collections/users/records/u1', json.dumps({'token': 'must-not-export'}), '2026-01-01 00:00:00Z'),
)
aux.commit()
aux.close()

media_dir = storage_dir / 'pbc_questions' / 'q1'
media_dir.mkdir(parents=True, exist_ok=True)
(media_dir / 'question.png').write_bytes(b'fixture-image')
(media_dir / 'question.png.attrs').write_text('{}', encoding='utf-8')
`;

  const result = spawnSync(python, ['-c', script], { encoding: 'utf8' });
  if (result.error) throw result.error;
  assert.equal(result.status, 0, `fixture setup should pass\nstdout=${result.stdout}\nstderr=${result.stderr}`);
}

const root = fs.mkdtempSync(path.join(os.tmpdir(), 'tiku-pb-sqlite-export-'));
const sqliteDir = path.join(root, 'pb');
const exportDir = path.join(root, 'export');
const storageDir = path.join(sqliteDir, 'storage');
fs.mkdirSync(storageDir, { recursive: true });
writeFixture(sqliteDir, storageDir);

const result = spawnSync(process.execPath, ['--import', 'tsx', exportScript], {
  cwd: repoRoot,
  encoding: 'utf8',
  env: {
    ...process.env,
    PB_SQLITE_DIR: sqliteDir,
    PB_EXPORT_DIR: exportDir,
  },
});

assert.equal(result.status, 0, `export should pass\nstdout=${result.stdout}\nstderr=${result.stderr}`);

const users = JSON.parse(fs.readFileSync(path.join(exportDir, 'users.json'), 'utf8'));
assert.equal(users.length, 1, 'users collection should export records');
assert.equal(users[0].phone, '13800000000', 'phone should be kept for migration matching');
assert.equal('password' in users[0], false, 'password should be removed from normal JSON export');
assert.equal('tokenKey' in users[0], false, 'token should be removed from normal JSON export');
assert.equal('wechatOpenId' in users[0], false, 'openid should be removed by default');
assert.deepEqual(users[0].stats, { answered: 3 }, 'JSON fields should be parsed');

const questions = JSON.parse(fs.readFileSync(path.join(exportDir, 'questions.json'), 'utf8'));
assert.deepEqual(questions[0].options, ['A', 'B'], 'question JSON fields should be parsed');

assert.ok(!fs.existsSync(path.join(exportDir, '_superusers.json')), 'system collections should not export by default');

const manifest = JSON.parse(fs.readFileSync(path.join(exportDir, 'sqlite-export-manifest.json'), 'utf8'));
assert.equal(manifest.collectionCount, 2, 'manifest should include exported collection count');
assert.equal(manifest.recordCount, 2, 'manifest should include exported record count');
assert.ok(
  manifest.collections.find(item => item.name === 'users')?.redactedFields.some(item => item.fieldPath === 'password'),
  'manifest should report redacted user password field',
);
assert.equal(manifest.storage.fileCount, 1, 'manifest should summarize storage files');
assert.equal(manifest.auxiliary.tables[0].name, '_logs', 'manifest should summarize auxiliary logs');

const storageManifest = JSON.parse(fs.readFileSync(path.join(exportDir, 'storage-manifest.json'), 'utf8'));
assert.equal(storageManifest.files[0].collectionName, 'questions', 'storage manifest should map collection id to name');
assert.ok(storageManifest.files[0].sha256, 'storage manifest should include file hash');

fs.rmSync(root, { recursive: true, force: true });

console.log('[PASS] PocketBase SQLite export');
