'use strict';

const express = require('express');
const os = require('os');
const router = express.Router();
const adminRouter = express.Router();

const { requireAdmin, requireAuth } = require('../middleware/auth');
const { generateLicenseKey } = require('../lib/license-key');
const licensesDb = require('../db/licenses');
const { CONN_PRICES, FREE_TIER } = require('../config/pricing');
const { sendTrialLicenseEmail } = require('../services/trial-license-email');
const db = require('../db/index');

// Release version — read from the root VERSION file, the SINGLE SOURCE OF TRUTH for
// the release version (same read as server.js APP_VERSION; package.json mirrors it).
// This is reported to the license server on every validate/status call, so it is our
// only view of what is actually running in the field — it must not drift. It used to
// read package.json, which sat at 1.0.0 while the shipped release was 1.2.2, so every
// self-hosted install phoned home a wrong version.
let APP_VERSION = 'unknown';
try {
  APP_VERSION = require('fs').readFileSync(require('path').join(__dirname, '..', 'VERSION'), 'utf8').trim() || 'unknown';
} catch (_) { /* ignore */ }

// Stable identifier for THIS self-hosted deployment. Locked onto the license on
// first validation; a second box with a different id is rejected.
function selfInstanceId() {
  return process.env.TUNEVAULT_INSTANCE_ID || os.hostname() || 'self-hosted';
}

/**
 * Core validation used by both POST /validate (external self-hosted callers)
 * and GET /status (this instance validating its own configured key).
 * Returns a plain result object — never throws for business-rule failures.
 */
async function validateLicense({ license_key, instance_id }) {
  if (!license_key) {
    return { valid: false, error: 'Invalid license key' };
  }

  const lic = await licensesDb.findByKey(license_key);
  if (!lic) {
    return { valid: false, error: 'Invalid license key' };
  }

  if (lic.status !== 'active') {
    return { valid: false, error: 'License suspended or expired' };
  }

  if (lic.expires_at && new Date(lic.expires_at) < new Date()) {
    await licensesDb.expireLicense(lic.id);
    return { valid: false, error: 'License expired' };
  }

  // Instance lock: if already registered to a different instance, reject.
  if (lic.instance_id && instance_id && lic.instance_id !== instance_id) {
    return { valid: false, error: 'License already in use on another instance' };
  }

  // First-time registration sets instance_id; always bump last_validated_at.
  await licensesDb.updateValidation(lic.id, instance_id);

  return {
    valid: true,
    tier: lic.plan_tier,
    max_connections: lic.max_connections,
    max_health_checks_per_month: lic.max_health_checks_per_month,
    expires_at: lic.expires_at,
  };
}

// GET /api/license/status — current license state for this instance.
// Cloud mode short-circuits. Self-hosted validates its configured key against
// the license server (tunevault.app) over HTTP, because the license rows live
// in the cloud DB where they were issued — not in the self-hosted local DB.
router.get('/status', async (req, res) => {
  const isSelfHosted = process.env.TUNEVAULT_SELF_HOSTED === 'true';
  const licenseKey = process.env.TUNEVAULT_LICENSE_KEY;

  if (!isSelfHosted) {
    return res.json({ mode: 'cloud', valid: true });
  }

  if (!licenseKey || !licenseKey.startsWith('TV-')) {
    return res.json({ mode: 'self-hosted', valid: false, error: 'No valid license key configured' });
  }

  const serverUrl = (process.env.TUNEVAULT_LICENSE_SERVER_URL || 'https://tunevault.app').replace(/\/$/, '');

  try {
    const resp = await fetch(`${serverUrl}/api/license/validate`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        license_key: licenseKey,
        instance_id: selfInstanceId(),
        version: APP_VERSION,
      }),
    });
    const result = await resp.json().catch(() => null);

    if (!result || !result.valid) {
      return res.json({ mode: 'self-hosted', valid: false, error: (result && result.error) || 'License validation failed' });
    }

    return res.json({
      mode: 'self-hosted',
      valid: true,
      tier: result.tier,
      max_connections: result.max_connections,
      max_health_checks_per_month: result.max_health_checks_per_month,
      expires_at: result.expires_at,
      key: licenseKey.substring(0, 8) + '...',
    });
  } catch (err) {
    console.error('[license] status remote validation error:', err.message);
    return res.json({ mode: 'self-hosted', valid: false, error: 'Could not reach license server' });
  }
});

// POST /api/license/validate — public; called by self-hosted instances.
router.post('/validate', async (req, res) => {
  try {
    const { license_key, instance_id } = req.body || {};
    const result = await validateLicense({ license_key, instance_id });
    return res.json(result);
  } catch (err) {
    console.error('[license] validate error:', err.message);
    return res.status(500).json({ valid: false, error: 'Validation error' });
  }
});

// POST /api/license/free — mint (or return) this user's free 30-day self-hosted
// license. requireAuth: a logged-in user gives us a verified email and a user row
// to hang the one-per-user rule on.
//
// Without this, /download is a dead end: the zip cannot start without a
// TUNEVAULT_LICENSE_KEY and nothing minted one short of a Razorpay payment.
//
// Idempotent by design AND by constraint: a unique partial index
// (self_hosted_licenses_one_free_per_user, created in server.js ensureColumns)
// makes a duplicate free key impossible even if two requests race the read below.
router.post('/free', requireAuth, async (req, res) => {
  const userId = req.user.id;

  try {
    const active = await licensesDb.listActiveByUserId(userId);

    // Already has a free key → hand back the same one. Never mint a second.
    const existingFree = active.find(l => l.plan_tier === FREE_TIER.plan_tier);
    if (existingFree) {
      return res.json({
        license_key : existingFree.license_key,
        expires_at  : existingFree.expires_at,
        already_issued: true,
      });
    }

    // Already paying → a trial is a downgrade, not a favour. `code` + `plan_tier`
    // let the UI render this as a friendly state rather than string-matching the
    // prose or dumping a raw 400 (public/free-license.js).
    const paid = active.find(l => l.plan_tier !== FREE_TIER.plan_tier);
    if (paid) {
      return res.status(400).json({
        error: `You already have an active ${paid.plan_tier} license — no trial needed. Your key was emailed to you; contact kiran@tunevault.app if you need it resent.`,
        code: 'paid_license_active',
        plan_tier: paid.plan_tier,
      });
    }

    const expiresAt = new Date(Date.now() + FREE_TIER.days * 24 * 60 * 60 * 1000);
    const licenseKey = generateLicenseKey(FREE_TIER.plan_tier);

    let created;
    try {
      created = await licensesDb.createLicense({
        userId,
        planTier       : FREE_TIER.plan_tier,
        maxConnections : FREE_TIER.max_connections,
        maxHealthChecks: FREE_TIER.max_health_checks_per_month,
        expiresAt,
        licenseKey,
      });
    } catch (insErr) {
      // 23505 = the one-free-per-user index fired, i.e. a concurrent request won
      // the race. Return that request's key rather than erroring.
      if (insErr.code === '23505') {
        const raced = (await licensesDb.listActiveByUserId(userId))
          .find(l => l.plan_tier === FREE_TIER.plan_tier);
        if (raced) {
          return res.json({ license_key: raced.license_key, expires_at: raced.expires_at, already_issued: true });
        }
      }
      throw insErr;
    }

    // Email the key. Non-fatal on failure, same as activatePlan() — the key is
    // already minted and the response shows it on-screen regardless.
    let emailed = false;
    try {
      const { rows } = await db.query('SELECT email, name FROM users WHERE id = $1', [userId]);
      const user = rows[0];
      if (user) {
        const result = await sendTrialLicenseEmail({
          userEmail : user.email,
          userName  : user.name,
          licenseKey: created.license_key,
          expiresAt : created.expires_at,
        });
        emailed = !!result.sent;
        if (!result.sent) {
          console.error(`[license] free trial email failed for user ${userId} (${user.email}): ${result.error}`);
        }
      }
    } catch (mailErr) {
      console.error(`[license] free trial email threw for user ${userId}:`, mailErr.message);
    }

    console.log(`[license] free 30-day license issued to user ${userId} (expires ${created.expires_at})`);
    return res.status(201).json({
      license_key : created.license_key,
      expires_at  : created.expires_at,
      max_connections: created.max_connections,
      emailed,
    });
  } catch (err) {
    console.error('[license] free issue error:', err.message);
    return res.status(500).json({ error: 'Failed to issue free license' });
  }
});

// POST /api/license/issue — admin only; mint a new self-hosted license.
router.post('/issue', requireAdmin, async (req, res) => {
  try {
    const { user_id, plan_tier, max_connections, max_health_checks_per_month, expires_at } = req.body || {};
    if (!plan_tier) {
      return res.status(400).json({ error: 'plan_tier is required' });
    }

    const licenseKey = generateLicenseKey(plan_tier);
    // Use explicit undefined checks so 0 / -1 (unlimited) are respected.
    // Default max_connections is tier-aware (from pricing config), falling back to 5.
    const maxConnections = (max_connections === undefined || max_connections === null)
      ? (CONN_PRICES[plan_tier]?.max_conns ?? 5)
      : max_connections;
    // Health-check-per-month cap is intentionally not enforced — NULL = unlimited.
    const maxHealthChecks = (max_health_checks_per_month === undefined || max_health_checks_per_month === null)
      ? null
      : max_health_checks_per_month;

    const created = await licensesDb.createLicense({
      userId: user_id || null,
      planTier: plan_tier,
      maxConnections,
      maxHealthChecks,
      expiresAt: expires_at || null,
      licenseKey,
    });

    return res.status(201).json({
      license_key: created.license_key,
      tier: created.plan_tier,
      max_connections: created.max_connections,
    });
  } catch (err) {
    console.error('[license] issue error:', err.message);
    return res.status(500).json({ error: 'Failed to issue license' });
  }
});

// GET /api/admin/licenses — admin only; list all licenses.
adminRouter.get('/', requireAdmin, async (req, res) => {
  try {
    const licenses = await licensesDb.listLicenses();
    return res.json({ licenses });
  } catch (err) {
    console.error('[license] list error:', err.message);
    return res.status(500).json({ error: 'Failed to list licenses' });
  }
});

module.exports = router;
module.exports.adminRouter = adminRouter;
