import { execSync } from 'child_process';
import path from 'path';
import crypto from 'crypto';

const ROOT = path.resolve(__dirname, '..', '..', '..');
const DEFAULT_E2E_DB = path.join(ROOT, 'tests', 'e2e', '.tmp', 'e2e-test.db');
const DB_PATH = process.env.MESHCORE_DATABASE_PATH ?? DEFAULT_E2E_DB;

interface SeedOptions {
  channelName: string;
  count: number;
  startTimestamp?: number;
  outgoingEvery?: number; // mark every Nth message as outgoing
  includePaths?: boolean;
}

interface SeedReadStateOptions {
  channelName: string;
  unreadCount: number;
}

function runPython(payload: object) {
  const b64 = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64');

const script = String.raw`python3 - <<'PY'
import base64, json, os, sqlite3, time

payload = json.loads(base64.b64decode(os.environ['PAYLOAD']).decode())
root = payload['root']
db_path = payload.get('db_path') or os.path.join(root, 'data', 'meshcore.db')
os.makedirs(os.path.dirname(db_path), exist_ok=True)
conn = sqlite3.connect(db_path)
conn.execute('PRAGMA journal_mode=WAL;')
conn.row_factory = sqlite3.Row


def upsert_channel(name: str, key_hex: str):
    conn.execute(
        """
        INSERT INTO channels (key, name, is_hashtag, on_radio)
        VALUES (?, ?, 1, 0)
        ON CONFLICT(key) DO UPDATE SET name=excluded.name
        """,
        (key_hex, name),
    )
    conn.commit()


def clear_channel_messages(key_hex: str):
    conn.execute("DELETE FROM messages WHERE conversation_key = ?", (key_hex,))
    conn.commit()


def seed_messages(key_hex: str, opts: dict):
    start_ts = int(opts.get('start_ts') or time.time())
    count = opts['count']
    outgoing_every = opts.get('out_every') or 0
    include_paths = bool(opts.get('paths'))
    for i in range(count):
        ts = start_ts + i
        text = f"seed-{i}"
        paths_json = None
        if include_paths and i % 5 == 0:
            paths_json = json.dumps([{"path": f"{i:02x}", "received_at": ts}])
        outgoing = 1 if (outgoing_every and (i % outgoing_every == 0)) else 0
        conn.execute(
            """
            INSERT INTO messages (type, conversation_key, text, sender_timestamp, received_at, paths, txt_type, signature, outgoing, acked)
            VALUES ('CHAN', ?, ?, ?, ?, ?, 0, NULL, ?, 0)
            """,
            (key_hex, text, ts, ts, paths_json, outgoing),
        )
    conn.commit()


def set_channel_last_read(key_hex: str, last_read: int | None):
    conn.execute("UPDATE channels SET last_read_at = ? WHERE key = ?", (last_read, key_hex))
    conn.commit()


def inject_raw_packet(hex_data: str, payload_hash: str):
    ts = int(time.time())
    data_blob = bytes.fromhex(hex_data)
    hash_blob = bytes.fromhex(payload_hash)
    conn.execute(
        """
        INSERT OR IGNORE INTO raw_packets (timestamp, data, payload_hash)
        VALUES (?, ?, ?)
        """,
        (ts, data_blob, hash_blob),
    )
    conn.commit()


if payload['action'] == 'seed_channel':
    name = payload['name']
    key_hex = payload['key_hex']
    upsert_channel(name, key_hex)
    clear_channel_messages(key_hex)
    seed_messages(key_hex, payload['opts'])
elif payload['action'] == 'seed_unread':
    name = payload['name']
    key_hex = payload['key_hex']
    upsert_channel(name, key_hex)
    clear_channel_messages(key_hex)
    # create unread messages
    now = int(time.time())
    for i in range(payload['unread']):
        ts = now - i
        text = f"unread-{i}"
        conn.execute(
            """
            INSERT INTO messages (type, conversation_key, text, sender_timestamp, received_at, paths, txt_type, signature, outgoing, acked)
            VALUES ('CHAN', ?, ?, ?, ?, NULL, 0, NULL, 0, 0)
            """,
            (key_hex, text, ts, ts),
        )
    set_channel_last_read(key_hex, now - 10_000)  # ensure unread
elif payload['action'] == 'inject_raw_packet':
    inject_raw_packet(payload['hex_data'], payload['payload_hash'])
else:
    raise SystemExit('unknown action')

conn.close()
PY`;

  execSync(script, {
    env: { ...process.env, PAYLOAD: b64 },
    stdio: 'inherit',
  });
}

function channelKeyFromName(name: string): string {
  // Matches backend: SHA256("#name").digest()[:16]
  const hash = crypto.createHash('sha256').update(name).digest('hex');
  return hash.slice(0, 32).toUpperCase();
}

export function seedChannelMessages(options: SeedOptions) {
  const keyHex = channelKeyFromName(
    options.channelName.startsWith('#') ? options.channelName : `#${options.channelName}`
  );
  runPython({
    action: 'seed_channel',
    root: ROOT,
    db_path: DB_PATH,
    name: options.channelName,
    key_hex: keyHex,
    opts: {
      count: options.count,
      start_ts: options.startTimestamp ?? Math.floor(Date.now() / 1000) - options.count,
      out_every: options.outgoingEvery ?? 0,
      paths: options.includePaths ?? false,
    },
  });
  return { key: keyHex };
}

interface EncryptedGroupTextOptions {
  channelName: string; // e.g. "test" — will be prefixed with # if needed
  senderName: string;
  messageText: string;
  timestamp?: number;
}

/**
 * Build a raw MeshCore GROUP_TEXT packet encrypted with the channel key,
 * matching the format expected by decoder.py `decrypt_group_text`.
 *
 * Packet layout:
 *   header(1) + path_len(1) + payload
 * Where payload = channel_hash(1) + mac(2) + ciphertext
 *
 * Header byte for FLOOD + GROUP_TEXT: route_type=0x01, payload_type=0x05 → (0x05 << 2) | 0x01 = 0x15
 */
function buildEncryptedGroupTextPacket(options: EncryptedGroupTextOptions): {
  rawHex: string;
  payloadHash: string;
} {
  const hashName = options.channelName.startsWith('#')
    ? options.channelName
    : `#${options.channelName}`;

  // channel_key = SHA256("#name")[:16]
  const channelKeyFull = crypto.createHash('sha256').update(hashName).digest();
  const channelKey = channelKeyFull.subarray(0, 16);

  // channel_hash = SHA256(channel_key)[0]
  const channelHash = crypto.createHash('sha256').update(channelKey).digest()[0];

  // Build plaintext: timestamp(4 LE) + flags(1) + "sender: message\0"
  const ts = options.timestamp ?? Math.floor(Date.now() / 1000);
  const tsBuf = Buffer.alloc(4);
  tsBuf.writeUInt32LE(ts, 0);
  const flagsBuf = Buffer.from([0x00]);
  const textStr = `${options.senderName}: ${options.messageText}\0`;
  const textBuf = Buffer.from(textStr, 'utf-8');

  const plainLen = 4 + 1 + textBuf.length;
  const paddedLen = Math.ceil(plainLen / 16) * 16;
  const plaintext = Buffer.alloc(paddedLen, 0);
  tsBuf.copy(plaintext, 0);
  flagsBuf.copy(plaintext, 4);
  textBuf.copy(plaintext, 5);

  // Encrypt: AES-128-ECB
  const cipher = crypto.createCipheriv('aes-128-ecb', channelKey, null);
  cipher.setAutoPadding(false);
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);

  // MAC: HMAC-SHA256(channel_key + 16_zero_bytes, ciphertext)[:2]
  const channelSecret = Buffer.concat([channelKey, Buffer.alloc(16, 0)]);
  const mac = crypto.createHmac('sha256', channelSecret).update(ciphertext).digest().subarray(0, 2);

  // Payload: channel_hash + mac + ciphertext
  const payload = Buffer.concat([Buffer.from([channelHash]), mac, ciphertext]);

  // Raw packet: header(0x15 = FLOOD + GROUP_TEXT) + path_len(0x00) + payload
  const rawPacket = Buffer.concat([Buffer.from([0x15, 0x00]), payload]);

  // payload_hash for dedup: SHA256 of the payload portion
  const payloadHash = crypto.createHash('sha256').update(payload).digest().toString('hex');

  return {
    rawHex: rawPacket.toString('hex'),
    payloadHash,
  };
}

/**
 * Build an encrypted GROUP_TEXT packet and inject it into the raw_packets table.
 */
export function injectEncryptedGroupText(options: EncryptedGroupTextOptions) {
  const { rawHex, payloadHash } = buildEncryptedGroupTextPacket(options);
  runPython({
    action: 'inject_raw_packet',
    root: ROOT,
    db_path: DB_PATH,
    hex_data: rawHex,
    payload_hash: payloadHash,
  });
  return { rawHex, payloadHash };
}

export function seedChannelUnread(options: SeedReadStateOptions) {
  const keyHex = channelKeyFromName(
    options.channelName.startsWith('#') ? options.channelName : `#${options.channelName}`
  );
  runPython({
    action: 'seed_unread',
    root: ROOT,
    db_path: DB_PATH,
    name: options.channelName,
    key_hex: keyHex,
    unread: options.unreadCount,
  });
  return { key: keyHex };
}
