import { BadRequestException, Injectable } from '@nestjs/common';
import { Kysely } from 'kysely';
import { DB } from '@/database/database.type';
import { Logger } from 'pino-nestjs';
import { sql } from 'kysely';
import {
  FEATURE_FLAG_KEYS,
  FEATURE_FLAG_DEFAULTS,
  FEATURE_FLAG_DESCRIPTIONS,
  FeatureFlagKey,
  FeatureFlagsMap,
  isFeatureFlagKey,
} from './feature-flags.constants';

const CACHE_TTL_MS = 15_000;

@Injectable()
export class FeatureFlagsService {
  private cache: { expiresAt: number; flags: FeatureFlagsMap } | null = null;

  constructor(
    private readonly db: Kysely<DB>,
    private readonly logger: Logger,
  ) {}

  private parseBoolean(value: unknown, fallback = false): boolean {
    if (typeof value === 'boolean') return value;
    if (typeof value === 'string') {
      const normalized = value.trim().toLowerCase();
      if (['true', '1', 'yes', 'on'].includes(normalized)) return true;
      if (['false', '0', 'no', 'off'].includes(normalized)) return false;
    }
    return fallback;
  }

  /**
   * Returns the full feature-flag map. Cached for 15s to avoid hammering
   * `app_settings` on every guarded request — mirrors the Express middleware.
   */
  async getFeatureFlags(options?: { useCache?: boolean }): Promise<FeatureFlagsMap> {
    const useCache = options?.useCache !== false;
    const now = Date.now();
    if (useCache && this.cache && this.cache.expiresAt > now) {
      return { ...this.cache.flags };
    }

    const flags: FeatureFlagsMap = { ...FEATURE_FLAG_DEFAULTS };

    try {
      const rows = await this.db
        .selectFrom('app_settings')
        .select(['key', 'value'])
        .where('key', 'in', FEATURE_FLAG_KEYS as unknown as string[])
        .where('deleted_at', 'is', null)
        .execute();

      for (const row of rows) {
        const key = row.key as FeatureFlagKey;
        flags[key] = this.parseBoolean(row.value, flags[key]);
      }

      this.cache = { expiresAt: now + CACHE_TTL_MS, flags: { ...flags } };
      return flags;
    } catch (error) {
      this.logger.error('[feature-flags] Failed to load flags, using defaults', error);
      return flags;
    }
  }

  async isEnabled(flag: FeatureFlagKey): Promise<boolean> {
    const flags = await this.getFeatureFlags();
    return flags[flag];
  }

  invalidateCache() {
    this.cache = null;
  }

  /**
   * Full flag map + human-readable definitions — backs `GET /admin/feature-flags`.
   * Always reads fresh (no cache) so admins see the true persisted state.
   */
  async listFeatureFlags() {
    const flags = await this.getFeatureFlags({ useCache: false });
    return { flags, definitions: { ...FEATURE_FLAG_DESCRIPTIONS } };
  }

  /**
   * Upserts a single flag into `app_settings` and invalidates the cache —
   * backs `PATCH /admin/feature-flags/:key`.
   */
  async updateFeatureFlag(input: { key: string; enabled: boolean; updatedBy?: string }) {
    const key = String(input.key || '').trim();
    if (!isFeatureFlagKey(key)) {
      throw new BadRequestException({ message: 'Invalid feature flag key', data: { key } });
    }

    const value = input.enabled ? 'true' : 'false';

    await sql`
      INSERT INTO app_settings (key, value, updated_at, deleted_at)
      VALUES (${key}, ${value}, NOW(), NULL)
      ON CONFLICT (key)
      DO UPDATE SET value = EXCLUDED.value,
                    updated_at = NOW(),
                    deleted_at = NULL
    `.execute(this.db);

    this.invalidateCache();
    const flags = await this.getFeatureFlags({ useCache: false });

    return {
      key: key as FeatureFlagKey,
      enabled: flags[key as FeatureFlagKey],
      updatedBy: input.updatedBy || null,
    };
  }
}
