#!/usr/bin/env node

const path = require('path');
const crypto = require('crypto');
let keytar = null;
try {
  keytar = require('keytar');
} catch (error) {
  keytar = null;
}

let electron;
try {
  ({ _electron: electron } = require('playwright'));
} catch (error) {
  console.error('Puuttuva riippuvuus: playwright');
  console.error('Asenna: npm install --save-dev playwright');
  process.exit(1);
}

const ROOT = path.resolve(__dirname, '..');
const AUTH_URL_DEFAULT = 'https://auth.kullervokapital.com';
const KEYTAR_SERVICE = 'Owalisaattori';
const SHARED_API_KEY_ACCOUNT = 'openai_api_key_shared';
const LEGACY_API_KEY_ACCOUNT = 'openai_api_key';

function env(name, fallback = '') {
  const value = String(process.env[name] || '').trim();
  return value || fallback;
}

function requireEnv(name) {
  const value = env(name);
  if (!value) {
    throw new Error(`Puuttuva ympäristömuuttuja: ${name}`);
  }
  return value;
}

function randomToken(len = 12) {
  return crypto.randomBytes(len).toString('hex').slice(0, len);
}

function createPassword(prefix = 'Tmp') {
  return `${prefix}-${randomToken(8)}-Aa!42`;
}

function buildManagedEmail(kind) {
  return `smoke.${kind}.${Date.now()}.${randomToken(6)}@example.local`;
}

function hasManualCredentials() {
  return !!(
    env('E2E_ADMIN_EMAIL') &&
    env('E2E_ADMIN_PASSWORD') &&
    env('E2E_USER_EMAIL') &&
    env('E2E_USER_PASSWORD')
  );
}

function logStep(text) {
  process.stdout.write(`[smoke] ${text}\n`);
}

async function snapshotSharedApiKeyState() {
  if (!keytar) return { supported: false };
  try {
    const shared = await keytar.getPassword(KEYTAR_SERVICE, SHARED_API_KEY_ACCOUNT);
    const legacy = await keytar.getPassword(KEYTAR_SERVICE, LEGACY_API_KEY_ACCOUNT);
    return {
      supported: true,
      shared: typeof shared === 'string' ? shared : '',
      legacy: typeof legacy === 'string' ? legacy : '',
      hasShared: !!shared,
      hasLegacy: !!legacy,
    };
  } catch (error) {
    return {
      supported: false,
      error: String(error?.message || error || 'snapshot_failed'),
    };
  }
}

async function restoreSharedApiKeyState(snapshot) {
  if (!snapshot?.supported || !keytar) return false;
  try {
    if (snapshot.hasShared) {
      await keytar.setPassword(KEYTAR_SERVICE, SHARED_API_KEY_ACCOUNT, snapshot.shared || '');
    } else {
      await keytar.deletePassword(KEYTAR_SERVICE, SHARED_API_KEY_ACCOUNT);
    }

    if (snapshot.hasLegacy) {
      await keytar.setPassword(KEYTAR_SERVICE, LEGACY_API_KEY_ACCOUNT, snapshot.legacy || '');
    } else {
      await keytar.deletePassword(KEYTAR_SERVICE, LEGACY_API_KEY_ACCOUNT);
    }
    return true;
  } catch (error) {
    throw new Error(`API-avaimen palautus epäonnistui: ${error?.message || error}`);
  }
}

async function waitForCondition(fn, timeoutMs = 20000, intervalMs = 200) {
  const start = Date.now();
  while (Date.now() - start < timeoutMs) {
    try {
      const value = await fn();
      if (value) return value;
    } catch (error) {
      // Poll until timeout.
    }
    await new Promise((resolve) => setTimeout(resolve, intervalMs));
  }
  throw new Error('Aikakatkaisu: ehto ei täyttynyt');
}

async function findWindowWithSelector(app, selector, timeoutMs = 20000) {
  return waitForCondition(async () => {
    const windows = app.windows();
    for (const win of windows) {
      const handle = await win.$(selector);
      if (handle) return win;
    }
    return null;
  }, timeoutMs);
}

async function isElementHidden(page, selector) {
  const handle = await page.$(selector);
  if (!handle) return true;
  return page.$eval(selector, (node) => {
    const style = window.getComputedStyle(node);
    return Boolean(node.hidden) || style.display === 'none' || style.visibility === 'hidden';
  });
}

async function waitTextContains(page, selector, expected, timeoutMs = 15000) {
  return waitForCondition(async () => {
    const text = await page.$eval(selector, (node) => node.textContent || '');
    return text.includes(expected) ? text : '';
  }, timeoutMs);
}

async function waitUserRowStatus(page, email, expectedStatus, timeoutMs = 15000) {
  return waitForCondition(async () => {
    return page.$$eval('#adminUsersTbody tr', (rows, payload) => {
      const needle = String(payload.email || '').trim().toLowerCase();
      const expected = String(payload.status || '').trim().toLowerCase();
      const row = rows.find((r) => {
        const emailCell = r.querySelector('td:nth-child(2)');
        return emailCell && (emailCell.textContent || '').trim().toLowerCase() === needle;
      });
      if (!row) return false;
      const statusCell = row.querySelector('td:nth-child(4)');
      if (!statusCell) return false;
      return (statusCell.textContent || '').trim().toLowerCase().includes(expected);
    }, { email, status: expectedStatus });
  }, timeoutMs);
}

async function loginWith(page, email, password) {
  await page.fill('#email', email);
  await page.fill('#password', password);
  await page.click('#login');
}

async function logoutFromMain(page) {
  await page.click('#logoutBtn');
}

async function parseResponse(response) {
  const raw = await response.text();
  let json = null;
  if (raw) {
    try {
      json = JSON.parse(raw);
    } catch (error) {
      json = null;
    }
  }
  return { raw, json };
}

function payloadMessage(payload, fallback) {
  const value = payload?.detail || payload?.error || payload?.message;
  const text = String(value || '').trim();
  return text || fallback;
}

async function authAdminRequest(authUrl, adminKey, method, pathName, body = null) {
  const response = await fetch(`${authUrl}${pathName}`, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'X-Admin-Key': adminKey,
    },
    body: body ? JSON.stringify(body) : undefined,
  });

  const payload = await parseResponse(response);
  if (!response.ok) {
    throw new Error(
      `Auth admin -kutsu epäonnistui (${method} ${pathName}, HTTP ${response.status}): ` +
      `${payloadMessage(payload.json, payload.raw || 'tuntematon virhe')}`
    );
  }

  return payload.json || {};
}

async function listAuthUsers(authUrl, adminKey) {
  const data = await authAdminRequest(authUrl, adminKey, 'GET', '/admin/users');
  return Array.isArray(data.users) ? data.users : [];
}

async function createAuthUser(authUrl, adminKey, { email, password, role }) {
  await authAdminRequest(authUrl, adminKey, 'POST', '/admin/users', { email, password, role });
}

async function deleteAuthUserById(authUrl, adminKey, userId) {
  await authAdminRequest(authUrl, adminKey, 'DELETE', `/admin/users/${userId}`);
}

async function cleanupCreatedUsers(authUrl, adminKey, emails) {
  const unique = Array.from(new Set(emails.filter(Boolean)));
  if (!unique.length) return;

  try {
    const users = await listAuthUsers(authUrl, adminKey);
    for (const email of unique) {
      const user = users.find((item) => String(item.email || '').trim().toLowerCase() === email.toLowerCase());
      if (!user || !user.id) continue;
      try {
        await deleteAuthUserById(authUrl, adminKey, Number(user.id));
        logStep(`Siivous: poistettu testikäyttäjä ${email}`);
      } catch (error) {
        logStep(`Siivous: käyttäjää ${email} ei voitu poistaa (${error.message || error})`);
      }
    }
  } catch (error) {
    logStep(`Siivous: käyttäjien listaus epäonnistui (${error.message || error})`);
  }
}

(async () => {
  let authAdminKey = '';
  let authUrl = '';
  let sharedApiKey = '';
  let managedUsers = true;
  let skipAdminMutations = false;
  let sharedApiKeySnapshot = null;

  const cleanupEmails = [];

  let credentials = {
    adminEmail: '',
    adminPassword: '',
    userEmail: '',
    userPassword: '',
  };

  let app = null;
  try {
    authAdminKey = requireEnv('E2E_AUTH_ADMIN_KEY');
    authUrl = env('E2E_AUTH_URL', env('OWALISAATTORI_AUTH_URL', AUTH_URL_DEFAULT));
    sharedApiKey = env('E2E_SHARED_API_KEY', 'sk-smoke-test-shared-key');
    managedUsers = env('E2E_SMOKE_MANAGED_USERS', '1') !== '0';
    skipAdminMutations = env('E2E_SMOKE_SKIP_ADMIN_MUTATIONS', '') === '1';

    sharedApiKeySnapshot = await snapshotSharedApiKeyState();
    if (sharedApiKeySnapshot?.supported) {
      logStep('Tallennetaan nykyinen yhteinen API-avain smoke-ajon ajaksi');
    } else {
      logStep('Varoitus: yhteisen API-avaimen varmuuskopiointi ei käytössä (keytar ei saatavilla)');
    }

    if (managedUsers) {
      credentials = {
        adminEmail: buildManagedEmail('admin'),
        adminPassword: createPassword('Admin'),
        userEmail: buildManagedEmail('user'),
        userPassword: createPassword('User'),
      };

      logStep('Luodaan kertakäyttöiset testikäyttäjät auth-palveluun');
      await createAuthUser(authUrl, authAdminKey, {
        email: credentials.adminEmail,
        password: credentials.adminPassword,
        role: 'admin',
      });
      await createAuthUser(authUrl, authAdminKey, {
        email: credentials.userEmail,
        password: credentials.userPassword,
        role: 'user',
      });

      cleanupEmails.push(credentials.adminEmail, credentials.userEmail);
      logStep(`Managed users valmis: ${credentials.adminEmail}, ${credentials.userEmail}`);
    } else {
      if (!hasManualCredentials()) {
        throw new Error(
          'Manuaalitila vaatii: E2E_ADMIN_EMAIL, E2E_ADMIN_PASSWORD, E2E_USER_EMAIL, E2E_USER_PASSWORD'
        );
      }

      credentials = {
        adminEmail: requireEnv('E2E_ADMIN_EMAIL'),
        adminPassword: requireEnv('E2E_ADMIN_PASSWORD'),
        userEmail: requireEnv('E2E_USER_EMAIL'),
        userPassword: requireEnv('E2E_USER_PASSWORD'),
      };
      logStep('Käytetään manuaalisia testikäyttäjiä ympäristömuuttujista');
    }

    logStep('Käynnistetään Electron-sovellus');
    app = await electron.launch({
      args: ['.'],
      cwd: ROOT,
      env: {
        ...process.env,
        OWALISAATTORI_DEV: '1',
      },
    });

    let mainWindow = await findWindowWithSelector(app, '#generateBtn', 5000).catch(() => null);
    if (mainWindow) {
      logStep('Avoin sessio löytyi, kirjaudutaan ulos');
      await logoutFromMain(mainWindow);
    }

    logStep('Kirjaudutaan user-roolilla');
    let loginWindow = await findWindowWithSelector(app, '#login');
    await loginWith(loginWindow, credentials.userEmail, credentials.userPassword);

    mainWindow = await findWindowWithSelector(app, '#generateBtn');

    const userApiHidden = await isElementHidden(mainWindow, '#apiKeySection');
    if (!userApiHidden) {
      throw new Error('RBAC-virhe: user näkee API-avainosion');
    }
    const userAdminCardHidden = await isElementHidden(mainWindow, '#adminUsersCard');
    if (!userAdminCardHidden) {
      throw new Error('RBAC-virhe: user näkee käyttäjähallinnan');
    }
    logStep('User-roolin RBAC OK');

    logStep('Vaihto admin-rooliin');
    await logoutFromMain(mainWindow);
    loginWindow = await findWindowWithSelector(app, '#login');
    await loginWith(loginWindow, credentials.adminEmail, credentials.adminPassword);

    mainWindow = await findWindowWithSelector(app, '#generateBtn');
    const adminApiHidden = await isElementHidden(mainWindow, '#apiKeySection');
    if (adminApiHidden) {
      throw new Error('RBAC-virhe: admin ei näe API-avainosiota');
    }
    logStep('Admin-roolin RBAC OK');

    logStep('Asetetaan yhteinen API-avain');
    await mainWindow.fill('#apiKeyInput', sharedApiKey);
    await mainWindow.click('#apiKeySaveBtn');
    await waitTextContains(mainWindow, '#statusText', 'Yhteinen API-avain tallennettu');

    if (!skipAdminMutations) {
      logStep('Huom: in-app käyttäjähallinta/audit poistettu, admin-mutaatiot tehdään OG-Administa');
    }

    logStep('Smoke OK');
  } catch (error) {
    console.error(`[smoke] FAIL: ${error.message || error}`);
    process.exitCode = 1;
  } finally {
    if (app) {
      try {
        await app.close();
      } catch (error) {
        // Ignore close failures during teardown.
      }
    }

    if (cleanupEmails.length) {
      logStep('Ajetaan testikäyttäjien siivous');
      await cleanupCreatedUsers(authUrl, authAdminKey, cleanupEmails);
    }

    if (sharedApiKeySnapshot) {
      try {
        const restored = await restoreSharedApiKeyState(sharedApiKeySnapshot);
        if (restored) {
          logStep('Palautettiin aiempi yhteinen API-avain smoke-ajon jälkeen');
        }
      } catch (error) {
        logStep(`VAROITUS: ${error.message || error}`);
      }
    }
  }

  if (process.exitCode && process.exitCode !== 0) {
    process.exit(process.exitCode);
  }
})();
