/**
 * Feature flags REST endpoints.
 *
 * GET    /flags           — list all flags (admin)
 * GET    /flags/poll      — polling endpoint for clients (returns enabled flags)
 * GET    /flags/:key      — get single flag
 * POST   /flags           — create flag
 * PUT    /flags/:key      — update flag
 * DELETE /flags/:key      — delete flag
 * POST   /flags/kill      — kill switch (disable all flags matching criteria)
 */

import type { FastifyInstance } from 'fastify';
import { getRequestProductId } from '../../lib/request-context.js';
import { BadRequestError, NotFoundError } from '../../lib/errors.js';
import * as repo from './repository.js';
import { CreateFlagSchema, UpdateFlagSchema, type FeatureFlagDoc } from './types.js';

/**
 * FNV-1a hash — deterministic 32-bit hash for user+flag assignment.
 * Same (userId, flagKey) pair always produces the same 0-99 bucket.
 */
function hashUserFlag(userId: string, flagKey: string): number {
  const input = `${userId}:${flagKey}`;
  let hash = 0x811c9dc5; // FNV offset basis
  for (let i = 0; i < input.length; i++) {
    hash ^= input.charCodeAt(i);
    hash = Math.imul(hash, 0x01000193); // FNV prime
  }
  return (hash >>> 0) % 100; // 0-99 bucket
}

/**
 * Compare two dot-separated version strings (e.g. "17.2.1" vs "18.0").
 * Returns -1 if a < b, 0 if equal, 1 if a > b.
 */
function compareVersions(a: string, b: string): number {
  const pa = a.split('.').map(Number);
  const pb = b.split('.').map(Number);
  const len = Math.max(pa.length, pb.length);
  for (let i = 0; i < len; i++) {
    const na = pa[i] ?? 0;
    const nb = pb[i] ?? 0;
    if (na < nb) return -1;
    if (na > nb) return 1;
  }
  return 0;
}

export { hashUserFlag, compareVersions };

export async function flagRoutes(app: FastifyInstance) {
  // List all flags
  app.get('/flags', async req => {
    const productId = getRequestProductId(req);
    return { flags: await repo.list(productId) };
  });

  // Polling endpoint for clients
  // ?userId=xxx      — deterministic hash ensures same user always gets same flag assignment
  // ?platform=xxx    — filter flags by platform (e.g. ios, android, web, desktop)
  // ?region=xxx      — filter flags by region (e.g. us, eu, apac)
  // ?osVersion=xxx   — filter flags by OS version (e.g. 17.2.1)
  app.get('/flags/poll', async req => {
    const productId = getRequestProductId(req);
    const { platform, userId, region, osVersion } = req.query as {
      platform?: string;
      userId?: string;
      region?: string;
      osVersion?: string;
    };
    const all = await repo.list(productId);
    const active = all.filter(f => {
      if (!f.enabled) return false;
      // Platform targeting: flag specifies which platforms it applies to
      if (f.platforms.length > 0 && platform && !f.platforms.includes(platform)) return false;
      // Region targeting: flag specifies which regions it applies to
      if (f.regions?.length > 0 && region && !f.regions.includes(region)) return false;
      // OS version targeting: flag specifies version ranges per platform
      if (f.osVersions?.length > 0 && platform && osVersion) {
        const rule = f.osVersions.find(r => r.platform === platform);
        if (rule) {
          if (rule.minVersion && compareVersions(osVersion, rule.minVersion) < 0) return false;
          if (rule.maxVersion && compareVersions(osVersion, rule.maxVersion) > 0) return false;
        } else {
          // osVersions defined but no rule for this platform — skip this flag
          return false;
        }
      }
      return true;
    });
    const flags: Record<string, boolean> = {};
    for (const f of active) {
      if (f.percentage >= 100) {
        flags[f.key] = true;
      } else if (f.percentage <= 0) {
        flags[f.key] = false;
      } else if (userId) {
        // Deterministic: same user+flag always gets the same result
        flags[f.key] = hashUserFlag(userId, f.key) < f.percentage;
      } else {
        // Fallback for anonymous requests (no userId)
        flags[f.key] = Math.random() * 100 < f.percentage;
      }
    }
    return { flags, productId };
  });

  // Get flag
  app.get('/flags/:key', async req => {
    const { key } = req.params as { key: string };
    const productId = getRequestProductId(req);
    const flag = await repo.getByKey(key, productId);
    if (!flag) throw new NotFoundError('Flag not found');
    return flag;
  });

  // Create flag
  app.post('/flags', async (req, reply) => {
    const productId = getRequestProductId(req);
    const parsed = CreateFlagSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const input = parsed.data;
    const existing = await repo.getByKey(input.key, productId);
    if (existing) throw new BadRequestError(`Flag "${input.key}" already exists`);

    const now = new Date().toISOString();
    const doc: FeatureFlagDoc = {
      id: `flag_${productId}_${input.key}`,
      productId,
      ...input,
      createdAt: now,
      updatedAt: now,
    };
    const created = await repo.create(doc);
    reply.code(201);
    return created;
  });

  // Update flag
  app.put('/flags/:key', async req => {
    const { key } = req.params as { key: string };
    const productId = getRequestProductId(req);
    const flag = await repo.getByKey(key, productId);
    if (!flag) throw new NotFoundError('Flag not found');

    const parsed = UpdateFlagSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const updated = await repo.update(flag.id, parsed.data);
    if (!updated) throw new NotFoundError('Flag update failed');
    return updated;
  });

  // Delete flag
  app.delete('/flags/:key', async req => {
    const { key } = req.params as { key: string };
    const productId = getRequestProductId(req);
    const flag = await repo.getByKey(key, productId);
    if (!flag) throw new NotFoundError('Flag not found');
    await repo.remove(flag.id);
    return { success: true };
  });

  // Kill switch — disable all flags matching optional platform filter
  app.post('/flags/kill', async req => {
    const { platform, keys } = req.body as { platform?: string; keys?: string[] };
    const productId = getRequestProductId(req);
    const all = await repo.list(productId);
    const toDisable = all.filter(f => {
      if (keys && keys.length > 0 && !keys.includes(f.key)) return false;
      if (platform && f.platforms.length > 0 && !f.platforms.includes(platform)) return false;
      return f.enabled;
    });

    const disabled: string[] = [];
    for (const f of toDisable) {
      await repo.update(f.id, { enabled: false });
      disabled.push(f.key);
    }
    return { disabled, count: disabled.length };
  });
}
