/**
 * License key REST endpoints.
 *
 * POST   /licenses/generate    — generate a new license key
 * POST   /licenses/activate    — activate license on a device
 * POST   /licenses/deactivate  — deactivate license on a device
 * GET    /licenses/status/:key — get license status
 * GET    /licenses/user/:userId — get user's licenses
 */

import type { FastifyInstance } from 'fastify';
import { getRequestProductId, getRequestProductConfig } from '../../lib/request-context.js';
import { BadRequestError, NotFoundError } from '../../lib/errors.js';
import * as authRepo from '../auth/repository.js';
import * as jwt from '../auth/jwt.js';
import * as repo from './repository.js';
import {
  GenerateLicenseSchema,
  ActivateLicenseSchema,
  DeactivateLicenseSchema,
  type LicenseDoc,
} from './types.js';

const LOCKOUT_WINDOW_MS = Number(process.env.LICENSE_ACTIVATE_LOCKOUT_WINDOW_MS || 15 * 60 * 1000);
const MAX_FAILED_ATTEMPTS = Number(process.env.LICENSE_ACTIVATE_MAX_FAILED_ATTEMPTS || 5);

const failedAttemptsByIp = new Map<string, { count: number; firstFailedAt: number }>();

function assertNotLockedOut(ip: string): void {
  const entry = failedAttemptsByIp.get(ip);
  if (!entry) return;
  if (Date.now() - entry.firstFailedAt > LOCKOUT_WINDOW_MS) {
    failedAttemptsByIp.delete(ip);
    return;
  }
  if (entry.count >= MAX_FAILED_ATTEMPTS) {
    throw new BadRequestError('Too many failed activation attempts. Please try again later.');
  }
}

function recordActivationFailure(ip: string): void {
  const now = Date.now();
  const entry = failedAttemptsByIp.get(ip);
  if (!entry || now - entry.firstFailedAt > LOCKOUT_WINDOW_MS) {
    failedAttemptsByIp.set(ip, { count: 1, firstFailedAt: now });
    return;
  }
  failedAttemptsByIp.set(ip, { count: entry.count + 1, firstFailedAt: entry.firstFailedAt });
}

function clearActivationFailures(ip: string): void {
  failedAttemptsByIp.delete(ip);
}

export async function licenseRoutes(app: FastifyInstance) {
  // Generate license
  app.post('/licenses/generate', async (req, reply) => {
    const parsed = GenerateLicenseSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const input = parsed.data;
    const now = new Date().toISOString();
    const productId = getRequestProductId(req);
    const productConfig = getRequestProductConfig(req);
    const key = repo.generateKey(productConfig.licensePrefix);

    const doc: LicenseDoc = {
      id: `lic_${crypto.randomUUID()}`,
      productId,
      key,
      userId: input.userId,
      plan: input.plan,
      status: 'active',
      activatedAt: null,
      expiresAt: input.expiresAt,
      deviceIds: [],
      maxDevices: input.maxDevices,
      createdAt: now,
      updatedAt: now,
    };
    const created = await repo.create(doc);
    reply.code(201);
    return created;
  });

  // Activate
  app.post('/licenses/activate', async req => {
    assertNotLockedOut(req.ip);
    const parsed = ActivateLicenseSchema.safeParse(req.body);
    if (!parsed.success) {
      recordActivationFailure(req.ip);
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const { key, deviceId } = parsed.data;
    const productId = getRequestProductId(req);
    const productConfig = getRequestProductConfig(req);
    const license = await repo.getByKey(key, productId);
    if (!license) {
      recordActivationFailure(req.ip);
      throw new NotFoundError('License not found');
    }
    if (license.status !== 'active') {
      recordActivationFailure(req.ip);
      throw new BadRequestError('License is not active');
    }
    if (license.expiresAt && new Date(license.expiresAt) < new Date()) {
      recordActivationFailure(req.ip);
      throw new BadRequestError('License has expired');
    }
    if (license.deviceIds.includes(deviceId)) {
      const user = await authRepo.getById(license.userId);
      if (!user || user.status !== 'active') {
        recordActivationFailure(req.ip);
        throw new BadRequestError('License owner account is missing or disabled');
      }
      clearActivationFailures(req.ip);
      const accessToken = await jwt.createAccessToken({
        sub: user.id,
        email: user.email,
        role: user.role,
        productId,
        plan: license.plan,
      });
      const refreshToken = await jwt.createRefreshToken({ sub: user.id, productId });
      return { license, accessToken, refreshToken }; // Already activated on this device
    }
    const planDeviceLimit = productConfig.deviceLimits[license.plan] ?? license.maxDevices;
    if (license.deviceIds.length >= planDeviceLimit) {
      recordActivationFailure(req.ip);
      throw new BadRequestError(
        `Maximum devices (${planDeviceLimit}) reached for ${license.plan} plan. Deactivate a device first.`
      );
    }

    const updated = await repo.update(license.id, license.userId, {
      deviceIds: [...license.deviceIds, deviceId],
      maxDevices: planDeviceLimit,
      activatedAt: license.activatedAt ?? new Date().toISOString(),
    });
    if (!updated) throw new NotFoundError('License update failed');

    const user = await authRepo.getById(license.userId);
    if (!user || user.status !== 'active') {
      recordActivationFailure(req.ip);
      throw new BadRequestError('License owner account is missing or disabled');
    }

    clearActivationFailures(req.ip);
    const accessToken = await jwt.createAccessToken({
      sub: user.id,
      email: user.email,
      role: user.role,
      productId,
      plan: updated.plan,
    });
    const refreshToken = await jwt.createRefreshToken({ sub: user.id, productId });

    return { license: updated, accessToken, refreshToken };
  });

  // Deactivate device
  app.post('/licenses/deactivate', async req => {
    const parsed = DeactivateLicenseSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const { key, deviceId } = parsed.data;
    const productId = getRequestProductId(req);
    const license = await repo.getByKey(key, productId);
    if (!license) throw new NotFoundError('License not found');

    const updated = await repo.update(license.id, license.userId, {
      deviceIds: license.deviceIds.filter(d => d !== deviceId),
    });
    if (!updated) throw new NotFoundError('License update failed');
    return updated;
  });

  // Revoke license
  app.post('/licenses/revoke', async req => {
    const { key } = req.body as { key: string };
    if (!key) throw new BadRequestError('key is required');
    const productId = getRequestProductId(req);
    const license = await repo.getByKey(key, productId);
    if (!license) throw new NotFoundError('License not found');
    if (license.status === 'revoked') return license;

    const updated = await repo.update(license.id, license.userId, {
      status: 'revoked',
      deviceIds: [],
    });
    if (!updated) throw new NotFoundError('License update failed');
    return updated;
  });

  // Status
  app.get('/licenses/status/:key', async req => {
    const { key } = req.params as { key: string };
    const productId = getRequestProductId(req);
    const license = await repo.getByKey(key, productId);
    if (!license) throw new NotFoundError('License not found');
    return {
      key: license.key,
      plan: license.plan,
      status: license.status,
      devicesUsed: license.deviceIds.length,
      maxDevices: license.maxDevices,
      expiresAt: license.expiresAt,
    };
  });

  // User licenses
  app.get('/licenses/user/:userId', async req => {
    const { userId } = req.params as { userId: string };
    const productId = getRequestProductId(req);
    const licenses = await repo.getByUserId(userId, productId);
    return { licenses };
  });
}
