const path = require('path');
const fs = require('fs');
const os = require('os');
const crypto = require('crypto');

const SUPERADMIN_DIR = path.join(__dirname, '..', '..', 'superadmin', 'server');
const SERVER_DIR = path.join(__dirname, '..');

const LicenseTokenGenerator = require(path.join(SUPERADMIN_DIR, 'src', 'services', 'LicenseTokenGenerator'));
const LicenseCache = require(path.join(SERVER_DIR, 'src', 'services', 'LicenseCacheService'));
const Database = require('better-sqlite3');

const TenantId = 'integration-test-tenant-' + Date.now();
const results = [];

function record(name, ok, detail) {
  results.push({ name, ok, detail });
  const tag = ok ? 'PASS' : 'FAIL';
  console.log(`${tag} ${name}${detail ? ' — ' + detail : ''}`);
}

function buildValidPayload(overrides) {
  const start = new Date('2026-01-01T00:00:00Z');
  const end = overrides && overrides.expired
    ? new Date('2020-01-01T00:00:00Z')
    : new Date('2027-01-01T00:00:00Z');
  return Object.assign({
    tenant_id: TenantId,
    tenant_code: 'TST',
    term_name: 'Term 1',
    academic_year: '2026',
    start_date: start.toISOString(),
    end_date: end.toISOString(),
    max_students: 100,
    max_staff: 20
  }, overrides || {});
}

function makeReq(pathname, method, headers) {
  return {
    method,
    path: pathname,
    headers: headers || {},
    tenantId: undefined
  };
}

function makeRes() {
  const headers = {};
  return {
    statusCode: 200,
    body: undefined,
    headers,
    setHeader(k, v) { headers[k] = v; },
    status(code) { this.statusCode = code; return this; },
    json(payload) { this.body = payload; return this; }
  };
}

async function main() {
  const generated = LicenseTokenGenerator.generateToken(buildValidPayload({}));
  record('T15 sign EdDSA', generated && generated.token && generated.token.split('.').length === 3, '3-segment token');
  const headerB64 = generated.token.split('.')[0];
  const header = JSON.parse(Buffer.from(headerB64, 'base64url').toString('utf8'));
  record('T15 header alg=EdDSA', header.alg === 'EdDSA' && header.typ === 'JWT', JSON.stringify(header));

  const verifyOk = LicenseTokenGenerator.verifyToken(generated.token);
  record('T15 verifyToken accepts valid token', verifyOk && verifyOk.isValid === true);

  const tampered = generated.token.slice(0, -2) + (generated.token.endsWith('A') ? 'B' : 'A');
  const verifyBad = LicenseTokenGenerator.verifyToken(tampered);
  record('T15 verifyToken rejects tampered signature', verifyBad && verifyBad.isValid === false);

  const hsHeader = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
  const hsPayload = Buffer.from(JSON.stringify({ sub: 'x' })).toString('base64url');
  const hsSig = Buffer.from('not-a-real-signature').toString('base64url');
  const hsToken = `${hsHeader}.${hsPayload}.${hsSig}`;
  const verifyHs = LicenseTokenGenerator.verifyToken(hsToken);
  record('T15 verifyToken rejects HS256', verifyHs && verifyHs.isValid === false, verifyHs && verifyHs.error);

  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lic-int-'));
  const dbPath = path.join(tmpDir, 'tenant.db');
  const db = new Database(dbPath);
  db.exec(`CREATE TABLE IF NOT EXISTS tenant_licenses (
    id TEXT PRIMARY KEY,
    tenant_id TEXT NOT NULL,
    term_name TEXT NOT NULL,
    academic_year TEXT NOT NULL,
    start_date TEXT NOT NULL,
    end_date TEXT NOT NULL,
    grace_period_days INTEGER DEFAULT 7,
    max_students INTEGER DEFAULT 1000,
    max_staff INTEGER DEFAULT 200,
    license_key TEXT NOT NULL,
    signed_license_token TEXT NOT NULL,
    algorithm TEXT DEFAULT 'EdDSA',
    status TEXT DEFAULT 'ACTIVE',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )`);
  db.prepare(`INSERT INTO tenant_licenses
    (id, tenant_id, term_name, academic_year, start_date, end_date, license_key, signed_license_token, algorithm, status)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
    'row-1', TenantId, 'Term 1', '2026', '2026-01-01', '2027-01-01',
    generated.license_key, generated.token, 'EdDSA', 'ACTIVE'
  );

  const expiredGenerated = LicenseTokenGenerator.generateToken(buildValidPayload({ expired: true }));
  const expiredTenant = 'expired-tenant-' + Date.now();
  db.prepare(`INSERT INTO tenant_licenses
    (id, tenant_id, term_name, academic_year, start_date, end_date, license_key, signed_license_token, algorithm, status)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
    'row-2', expiredTenant, 'Term 0', '2025', '2019-01-01', '2020-01-01',
    expiredGenerated.license_key, expiredGenerated.token, 'EdDSA', 'ACTIVE'
  );
  db.close();

  process.env.DB_PATH = dbPath;
  process.env.SUPERADMIN_PUBLIC_KEY = LicenseTokenGenerator.getKeyPair().publicKey;

  const fetched = await LicenseCache.get(TenantId);
  record('T16 cache miss returns null on fresh tenant', fetched === null);

  await LicenseCache.set(TenantId, JSON.parse(Buffer.from(generated.token.split('.')[1], 'base64url').toString('utf8')), 120);
  const cached = await LicenseCache.get(TenantId);
  record('T16 cache set/get returns payload', cached && cached.iss === 'super_admin_licensing_engine');
  await LicenseCache.del(TenantId);
  const cleared = await LicenseCache.get(TenantId);
  record('T16 cache del clears entry', cleared === null);

  const LicenseTokenMiddleware = require(path.join(SERVER_DIR, 'src', 'middleware', 'licensingMiddleware'));
  const verifyMiddleware = LicenseTokenMiddleware.verifyLicenseMiddleware || LicenseTokenMiddleware;
  const updateSync = LicenseTokenMiddleware.updateSystemClockSync;

  let nextCalls = 0;
  const next = () => { nextCalls += 1; };

  const validGet = await runMiddleware(verifyMiddleware, makeReq('/api/students', 'GET', { 'x-tenant-id': TenantId }), makeRes(), next);
  record('T16 GET valid token allows next() with ACTIVE header',
    validGet.nextCalled && validGet.res.statusCode !== 402 && validGet.res.headers['X-License-Status'] === 'ACTIVE',
    `nextCalled=${validGet.nextCalled} status=${validGet.res.statusCode} header=${validGet.res.headers['X-License-Status']}`);

  nextCalls = 0;
  const validPost = await runMiddleware(verifyMiddleware, makeReq('/api/students', 'POST', { 'x-tenant-id': TenantId }), makeRes(), next);
  record('T16 POST valid token allows next()',
    validPost.nextCalled && validPost.res.statusCode !== 402);

  nextCalls = 0;
  await LicenseCache.del(expiredTenant);
  const expiredGet = await runMiddleware(verifyMiddleware, makeReq('/api/legacy', 'GET', { 'x-tenant-id': expiredTenant }), makeRes(), next);
  record('T16 GET expired token allows read-only next() with restricted header',
    expiredGet.nextCalled && expiredGet.res.statusCode !== 402 && expiredGet.res.headers['X-License-Status'] && /Restricted|Expired/i.test(expiredGet.res.headers['X-License-Status']),
    `header=${expiredGet.res.headers['X-License-Status']}`);

  nextCalls = 0;
  await LicenseCache.del(expiredTenant);
  const expiredPost = await runMiddleware(verifyMiddleware, makeReq('/api/legacy', 'POST', { 'x-tenant-id': expiredTenant }), makeRes(), next);
  record('T16 POST expired token returns 402 LICENSE_EXPIRED',
    !expiredPost.nextCalled && expiredPost.res.statusCode === 402 && expiredPost.res.body && expiredPost.res.body.code === 'LICENSE_EXPIRED',
    `code=${expiredPost.res.body && expiredPost.res.body.code}`);

  nextCalls = 0;
  const bypassLogin = await runMiddleware(verifyMiddleware, makeReq('/api/auth/login', 'POST'), makeRes(), next);
  record('T16 bypass path /api/auth/login allows next()', bypassLogin.nextCalled);

  nextCalls = 0;
  const bypassLicense = await runMiddleware(verifyMiddleware, makeReq('/api/license/current', 'GET'), makeRes(), next);
  record('T16 bypass path /api/license/current allows next()', bypassLicense.nextCalled);

  nextCalls = 0;
  const optionsCall = await runMiddleware(verifyMiddleware, makeReq('/api/students', 'OPTIONS'), makeRes(), next);
  record('T16 OPTIONS request bypassed', optionsCall.nextCalled);

  nextCalls = 0;
  const missingTenant = 'no-license-' + Date.now();
  const missingPost = await runMiddleware(verifyMiddleware, makeReq('/api/anything', 'POST', { 'x-tenant-id': missingTenant }), makeRes(), next);
  record('T16 POST missing license returns 402 NO_LICENSE',
    !missingPost.nextCalled && missingPost.res.statusCode === 402 && missingPost.res.body && missingPost.res.body.code === 'NO_LICENSE',
    `code=${missingPost.res.body && missingPost.res.body.code}`);

  if (typeof updateSync === 'function') {
    const remoteTs = new Date(Date.now() - 600000).toISOString();
    updateSync(remoteTs);
    nextCalls = 0;
    const driftPost = await runMiddleware(verifyMiddleware, makeReq('/api/anything', 'POST', { 'x-tenant-id': TenantId }), makeRes(), next);
    record('T16 clock drift > 300s blocks POST with 402 CLOCK_DRIFT_DETECTED',
      !driftPost.nextCalled && driftPost.res.statusCode === 402 && driftPost.res.body && driftPost.res.body.code === 'CLOCK_DRIFT_DETECTED',
      `code=${driftPost.res.body && driftPost.res.body.code}`);

    nextCalls = 0;
    const driftGet = await runMiddleware(verifyMiddleware, makeReq('/api/anything', 'GET', { 'x-tenant-id': TenantId }), makeRes(), next);
    record('T16 clock drift still allows GET with restricted header',
      driftGet.nextCalled && driftGet.res.headers['X-License-Status'] && driftGet.res.headers['X-License-Status'].includes('Drift'),
      `header=${driftGet.res.headers['X-License-Status']}`);
  } else {
    record('T16 updateSystemClockSync export', false, 'missing named export');
  }

  try { await LicenseCache._close(); } catch (_) {}
  try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (_) {}

  const failed = results.filter(r => !r.ok);
  console.log(`\nFINAL: ${results.length - failed.length}/${results.length} passed`);
  process.exit(failed.length === 0 ? 0 : 1);
}

function runMiddleware(mw, req, res, next) {
  return new Promise((resolve) => {
    let nextCalled = false;
    let finished = false;
    const finish = (extra) => {
      if (finished) return;
      finished = true;
      resolve(Object.assign({ nextCalled, res }, extra || {}));
    };
    const captureNext = (...args) => {
      nextCalled = true;
      if (typeof next === 'function') next(...args);
      finish();
    };
    const captureRes = {
      setHeader: (k, v) => { res.setHeader(k, v); return captureRes; },
      status: (code) => { res.statusCode = code; return captureRes; },
      json: (payload) => { res.body = payload; finish(); return captureRes; },
      headers: res.headers
    };
    try {
      const maybe = mw(req, captureRes, captureNext);
      Promise.resolve(maybe).catch((err) => {
        console.error('middleware threw:', err.message);
        finish();
      });
    } catch (err) {
      console.error('middleware sync threw:', err.message);
      finish();
    }
    setTimeout(() => finish({ timeout: true }), 3000);
  });
}

main().catch((err) => {
  console.error('Integration test crashed:', err);
  process.exit(1);
});