const fs = require('fs');
const path = require('path');
const { DatabaseSync } = require('node:sqlite');
const bcrypt = require('bcryptjs');
const { expandRuleSet } = require('./rule-catalog');

const DATA_DIR = process.env.MIXEDASSETS_DATA_DIR || path.join(process.cwd(), 'data');
const DB_PATH = process.env.MIXEDASSETS_DB_PATH || path.join(DATA_DIR, 'mixedassets.sqlite');

fs.mkdirSync(DATA_DIR, { recursive: true });

const db = new DatabaseSync(DB_PATH);
db.exec('PRAGMA foreign_keys = ON;');
db.exec('PRAGMA journal_mode = WAL;');

function now() {
  return new Date().toISOString();
}

function json(value, fallback = null) {
  if (value === undefined) return fallback === null ? null : JSON.stringify(fallback);
  return JSON.stringify(value);
}

function parseJson(value, fallback = null) {
  if (value === null || value === undefined || value === '') return fallback;
  try {
    return JSON.parse(value);
  } catch {
    return fallback;
  }
}

function bind(statement, params) {
  if (Array.isArray(params)) return statement;
  return statement;
}

function args(params) {
  if (Array.isArray(params)) return params;
  if (params && Object.keys(params).length > 0) return [params];
  return [];
}

function one(sql, params = {}) {
  const statement = bind(db.prepare(sql), params);
  return statement.get(...args(params));
}

function all(sql, params = {}) {
  const statement = bind(db.prepare(sql), params);
  return statement.all(...args(params));
}

function run(sql, params = {}) {
  const statement = bind(db.prepare(sql), params);
  return statement.run(...args(params));
}

function tx(fn) {
  db.exec('BEGIN IMMEDIATE');
  try {
    const result = fn();
    db.exec('COMMIT');
    return result;
  } catch (error) {
    db.exec('ROLLBACK');
    throw error;
  }
}

function initialize() {
  db.exec(`
    CREATE TABLE IF NOT EXISTS teams (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL UNIQUE,
      description TEXT,
      created_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      team_id INTEGER REFERENCES teams(id),
      name TEXT NOT NULL,
      email TEXT NOT NULL UNIQUE,
      password_hash TEXT NOT NULL,
      role TEXT NOT NULL CHECK(role IN ('admin','finance','operations','viewer')),
      status TEXT NOT NULL DEFAULT 'active',
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS user_preferences (
      user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
      preferences_json TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS entities (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      legal_name TEXT,
      tax_id TEXT,
      fiscal_year_end_month INTEGER NOT NULL DEFAULT 12,
      base_currency TEXT NOT NULL DEFAULT 'USD',
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS reference_values (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      type TEXT NOT NULL,
      name TEXT NOT NULL,
      code TEXT,
      metadata TEXT,
      UNIQUE(type, name)
    );

    CREATE TABLE IF NOT EXISTS employees (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      workday_worker_id TEXT UNIQUE,
      employee_number TEXT,
      name TEXT NOT NULL,
      email TEXT,
      department TEXT,
      location TEXT,
      manager_name TEXT,
      status TEXT NOT NULL DEFAULT 'active',
      source TEXT NOT NULL DEFAULT 'manual',
      source_payload TEXT,
      last_synced_at TEXT,
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS assets (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      asset_id TEXT NOT NULL UNIQUE,
      entity_id INTEGER REFERENCES entities(id),
      template_id INTEGER,
      description TEXT NOT NULL,
      category TEXT NOT NULL,
      status TEXT NOT NULL DEFAULT 'in_service',
      acquisition_cost REAL NOT NULL DEFAULT 0,
      acquired_date TEXT,
      in_service_date TEXT,
      useful_life_months INTEGER NOT NULL DEFAULT 60,
      salvage_value REAL NOT NULL DEFAULT 0,
      land_value REAL NOT NULL DEFAULT 0,
      prior_depreciation REAL NOT NULL DEFAULT 0,
      location TEXT,
      department TEXT,
      group_name TEXT,
      custodian TEXT,
      vendor TEXT,
      serial_number TEXT,
      invoice_number TEXT,
      gl_asset_account TEXT,
      gl_expense_account TEXT,
      gl_accumulated_account TEXT,
      barcode_value TEXT,
      condition TEXT,
      new_or_used TEXT DEFAULT 'new',
      listed_property INTEGER NOT NULL DEFAULT 0,
      business_use_percent REAL NOT NULL DEFAULT 100,
      investment_use_percent REAL NOT NULL DEFAULT 0,
      disposal_date TEXT,
      disposal_price REAL,
      disposal_expense REAL,
      disposal_type TEXT,
      property_type TEXT,
      notes TEXT,
      custom_fields TEXT,
      created_by INTEGER REFERENCES users(id),
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS asset_books (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      asset_id INTEGER NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
      book_type TEXT NOT NULL,
      active INTEGER NOT NULL DEFAULT 1,
      cost REAL,
      depreciation_method TEXT NOT NULL DEFAULT 'straight_line',
      convention TEXT NOT NULL DEFAULT 'half_year',
      useful_life_months INTEGER,
      section_179_amount REAL NOT NULL DEFAULT 0,
      bonus_percent REAL NOT NULL DEFAULT 0,
      business_use_percent REAL,
      investment_use_percent REAL,
      manual_depreciation TEXT,
      UNIQUE(asset_id, book_type)
    );

    CREATE TABLE IF NOT EXISTS asset_templates (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL UNIQUE,
      description TEXT,
      defaults TEXT NOT NULL,
      custom_fields TEXT NOT NULL,
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS tax_rule_sets (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      jurisdiction TEXT NOT NULL,
      name TEXT NOT NULL,
      effective_date TEXT NOT NULL,
      version TEXT NOT NULL,
      rules_json TEXT NOT NULL,
      active INTEGER NOT NULL DEFAULT 1,
      source_note TEXT,
      created_at TEXT NOT NULL,
      UNIQUE(jurisdiction, version)
    );

    CREATE TABLE IF NOT EXISTS leases (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      asset_id INTEGER REFERENCES assets(id) ON DELETE CASCADE,
      lessor TEXT,
      contract_value REAL NOT NULL DEFAULT 0,
      start_date TEXT,
      end_date TEXT,
      discount_rate REAL NOT NULL DEFAULT 0,
      payment_frequency TEXT NOT NULL DEFAULT 'monthly',
      notes TEXT,
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS warranties (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      asset_id INTEGER NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
      provider TEXT,
      phone TEXT,
      start_date TEXT,
      expiration_date TEXT,
      warranty_limit TEXT,
      actual_usage TEXT,
      coverage_details TEXT,
      document_name TEXT,
      document_base64 TEXT,
      created_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS asset_files (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      asset_id INTEGER NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
      file_name TEXT NOT NULL,
      mime_type TEXT NOT NULL,
      size INTEGER NOT NULL,
      content_base64 TEXT NOT NULL,
      uploaded_by INTEGER REFERENCES users(id),
      uploaded_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS tasks (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      asset_id INTEGER REFERENCES assets(id) ON DELETE CASCADE,
      title TEXT NOT NULL,
      type TEXT NOT NULL DEFAULT 'maintenance',
      status TEXT NOT NULL DEFAULT 'open',
      due_date TEXT,
      assigned_to INTEGER REFERENCES users(id),
      notes TEXT,
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS checkouts (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      asset_id INTEGER NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
      checked_out_to TEXT NOT NULL,
      destination TEXT,
      due_date TEXT,
      checked_out_at TEXT NOT NULL,
      checked_in_at TEXT,
      notes TEXT
    );

    CREATE TABLE IF NOT EXISTS asset_assignments (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      asset_id INTEGER NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
      employee_id INTEGER REFERENCES employees(id),
      department TEXT,
      location TEXT,
      status TEXT NOT NULL DEFAULT 'assigned',
      assigned_at TEXT NOT NULL,
      released_at TEXT,
      assigned_by INTEGER REFERENCES users(id),
      release_reason TEXT,
      notes TEXT,
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS workday_connections (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL UNIQUE,
      tenant TEXT,
      base_url TEXT,
      token_url TEXT,
      client_id TEXT,
      client_secret TEXT,
      workers_path TEXT NOT NULL DEFAULT '/workers',
      enabled INTEGER NOT NULL DEFAULT 0,
      last_sync_at TEXT,
      last_sync_status TEXT,
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS saved_reports (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      report_type TEXT NOT NULL,
      options_json TEXT NOT NULL,
      created_by INTEGER REFERENCES users(id),
      created_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS app_settings (
      key TEXT PRIMARY KEY,
      value TEXT NOT NULL,
      updated_at TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS audit_logs (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      actor_id INTEGER REFERENCES users(id),
      action TEXT NOT NULL,
      entity_type TEXT NOT NULL,
      entity_id TEXT,
      before_json TEXT,
      after_json TEXT,
      created_at TEXT NOT NULL
    );
  `);

  seed();
}

function seed() {
  const createdAt = now();
  if (!one('SELECT id FROM teams LIMIT 1')) {
    run('INSERT INTO teams (name, description, created_at) VALUES (?, ?, ?)', [
      'Finance Operations',
      'Default MixedAssets team',
      createdAt
    ]);
  }

  const team = one('SELECT id FROM teams WHERE name = ?', ['Finance Operations']);
  if (!one('SELECT id FROM users LIMIT 1')) {
    run(
      'INSERT INTO users (team_id, name, email, password_hash, role, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)',
      [
        team.id,
        'MixedAssets Admin',
        'admin@mixedassets.local',
        bcrypt.hashSync(process.env.MIXEDASSETS_ADMIN_PASSWORD || 'ChangeMe123!', 12),
        'admin',
        createdAt,
        createdAt
      ]
    );
  }

  if (!one('SELECT id FROM entities LIMIT 1')) {
    run(
      'INSERT INTO entities (name, legal_name, fiscal_year_end_month, base_currency, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
      ['Demo Company', 'Demo Company LLC', 12, 'USD', createdAt, createdAt]
    );
  }

  const refs = [
    ['location', 'Headquarters', 'HQ'],
    ['location', 'Warehouse', 'WH'],
    ['department', 'Accounting', 'ACCT'],
    ['department', 'Operations', 'OPS'],
    ['category', 'Computer Equipment', 'COMP'],
    ['category', 'Office Furniture', 'FURN'],
    ['category', 'Vehicles', 'AUTO'],
    ['category', 'Leasehold Improvements', 'LHI']
  ];
  for (const item of refs) {
    run('INSERT OR IGNORE INTO reference_values (type, name, code, metadata) VALUES (?, ?, ?, ?)', [
      item[0],
      item[1],
      item[2],
      '{}'
    ]);
  }

  run(
    `INSERT OR IGNORE INTO employees (
      employee_number, name, email, department, location, manager_name, status, source, created_at, updated_at
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
    ['E-1001', 'Jordan Avery', 'jordan.avery@example.com', 'Operations', 'Headquarters', 'MixedAssets Admin', 'active', 'seed', createdAt, createdAt]
  );

  run(
    `INSERT OR IGNORE INTO workday_connections (
      name, tenant, base_url, token_url, workers_path, enabled, created_at, updated_at
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
    ['Primary Workday', '', '', '', '/workers', 0, createdAt, createdAt]
  );

  const starterTemplate = {
    category: 'Computer Equipment',
    useful_life_months: 60,
    depreciation_method: 'macrs_gds_200db_5',
    convention: 'half_year',
    gl_asset_account: '1500',
    gl_expense_account: '6400',
    gl_accumulated_account: '1590',
    business_use_percent: 100
  };
  run(
    'INSERT OR IGNORE INTO asset_templates (name, description, defaults, custom_fields, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
    [
      'Laptop or workstation',
      'Standard computer equipment template with 5-year tax life.',
      json(starterTemplate),
      json([
        { key: 'assigned_employee_id', label: 'Employee ID', type: 'text', required: false },
        { key: 'software_profile', label: 'Software profile', type: 'text', required: false }
      ]),
      createdAt,
      createdAt
    ]
  );

  const federalPath = path.join(process.cwd(), 'data', 'tax-rules', 'federal-2026.json');
  if (fs.existsSync(federalPath)) {
    const rules = expandRuleSet(JSON.parse(fs.readFileSync(federalPath, 'utf8')));
    run(
      `INSERT INTO tax_rule_sets (jurisdiction, name, effective_date, version, rules_json, active, source_note, created_at)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?)
       ON CONFLICT(jurisdiction, version) DO UPDATE SET
         name = excluded.name,
         effective_date = excluded.effective_date,
         rules_json = excluded.rules_json,
         active = excluded.active,
         source_note = excluded.source_note`,
      [
        rules.jurisdiction || 'US-FED',
        rules.name || 'Federal starter depreciation rules',
        rules.effectiveDate || rules.effective_date || '2026-01-01',
        rules.version || '2026.2',
        json(rules),
        1,
        rules.sourceNote || 'Starter rule template. Confirm current tax law before production filing.',
        createdAt
      ]
    );
  }

  const settings = {
    server_fqdn: process.env.MIXEDASSETS_SERVER_FQDN || 'localhost',
    cors_allowed_domains: process.env.MIXEDASSETS_CORS_ORIGINS || 'http://localhost:5173',
    database_driver: 'node:sqlite',
    database_path: DB_PATH,
    default_books: 'GAAP,FEDERAL,STATE,AMT,ACE,USER'
  };
  for (const [key, value] of Object.entries(settings)) {
    run('INSERT OR IGNORE INTO app_settings (key, value, updated_at) VALUES (?, ?, ?)', [key, String(value), createdAt]);
  }
}

function audit(actorId, action, entityType, entityId, beforeValue, afterValue) {
  run(
    'INSERT INTO audit_logs (actor_id, action, entity_type, entity_id, before_json, after_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)',
    [actorId || null, action, entityType, entityId === undefined ? null : String(entityId), json(beforeValue), json(afterValue), now()]
  );
}

module.exports = {
  DB_PATH,
  all,
  audit,
  db,
  initialize,
  json,
  now,
  one,
  parseJson,
  run,
  tx
};
