import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto';
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
import { dirname } from 'node:path';

const PROFILE_VERSION = 1;
const ALLOWED_RISKS = new Set(['weather', 'conflict', 'infrastructure', 'cyber', 'travel', 'health', 'crime', 'economic']);
const ALLOWED_DEPENDENCIES = new Set(['electricity', 'internet', 'mobile_network', 'public_transport', 'private_vehicle', 'medical_power', 'mobility_support']);

export class SecurityProfileStore {
  constructor(filePath, encryptionSecret) {
    this.filePath = filePath;
    this.encryptionSecret = String(encryptionSecret || '');
    this.profile = null;
    this.configured = this.encryptionSecret.length >= 32;
    this.available = this.configured;
    this.reason = this.available ? null : 'SECURITY_PROFILE_ENCRYPTION_KEY must contain at least 32 characters';
  }

  init() {
    mkdirSync(dirname(this.filePath), { recursive: true });
    if (!this.available || !existsSync(this.filePath)) return this;
    try {
      this.profile = decryptEnvelope(readFileSync(this.filePath, 'utf8'), this.encryptionSecret);
      this.profile = sanitizeSecurityProfile(this.profile);
    } catch (error) {
      this.profile = null;
      this.available = false;
      this.reason = `Encrypted profile could not be read: ${error.message}`;
    }
    return this;
  }

  get exists() {
    return Boolean(this.profile?.completedAt);
  }

  getProfile() {
    return this.profile ? structuredClone(this.profile) : null;
  }

  getAgentProfile() {
    const profile = this.getProfile();
    if (!profile) return null;
    return {
      language: profile.language,
      preferredName: profile.preferredName || null,
      location: profile.location,
      timezone: profile.timezone || null,
      household: profile.household,
      mobility: profile.mobility,
      travelPattern: profile.travelPattern || null,
      riskPriorities: profile.riskPriorities,
      criticalDependencies: profile.criticalDependencies,
      alertPreference: profile.alertPreference,
      quietHours: profile.quietHours || null,
      consentedAt: profile.consentedAt,
      updatedAt: profile.updatedAt,
    };
  }

  save(input) {
    if (!this.available) throw new Error(this.reason);
    const now = new Date().toISOString();
    const profile = sanitizeSecurityProfile({
      ...input,
      version: PROFILE_VERSION,
      completedAt: input.completedAt || now,
      updatedAt: now,
    });
    const envelope = encryptEnvelope(profile, this.encryptionSecret);
    const temporaryPath = `${this.filePath}.tmp`;
    writeFileSync(temporaryPath, envelope, { encoding: 'utf8', mode: 0o600 });
    renameSync(temporaryPath, this.filePath);
    this.profile = profile;
    this.reason = null;
    return this.getProfile();
  }

  delete() {
    if (existsSync(this.filePath)) unlinkSync(this.filePath);
    this.profile = null;
    this.available = this.configured;
    this.reason = this.available ? null : 'SECURITY_PROFILE_ENCRYPTION_KEY must contain at least 32 characters';
  }

  status() {
    return {
      available: this.available,
      configured: this.configured,
      exists: this.exists,
      encrypted: true,
      reason: this.reason,
      updatedAt: this.profile?.updatedAt || null,
    };
  }
}

export function sanitizeSecurityProfile(input = {}) {
  return {
    version: PROFILE_VERSION,
    language: ['de', 'en'].includes(input.language) ? input.language : 'en',
    preferredName: clean(input.preferredName, 80) || null,
    location: {
      country: clean(input.location?.country, 80) || null,
      region: clean(input.location?.region, 100) || null,
      city: clean(input.location?.city, 100) || null,
    },
    timezone: clean(input.timezone, 80) || null,
    household: {
      adults: boundedInt(input.household?.adults, 0, 20, 1),
      children: boundedInt(input.household?.children, 0, 20, 0),
      pets: boundedInt(input.household?.pets, 0, 20, 0),
    },
    mobility: cleanList(input.mobility, 8, 40),
    travelPattern: clean(input.travelPattern, 120) || null,
    riskPriorities: cleanList(input.riskPriorities, 8, 40).filter(item => ALLOWED_RISKS.has(item)),
    criticalDependencies: cleanList(input.criticalDependencies, 8, 40).filter(item => ALLOWED_DEPENDENCIES.has(item)),
    alertPreference: ['critical_only', 'important', 'all'].includes(input.alertPreference) ? input.alertPreference : 'important',
    quietHours: clean(input.quietHours, 40) || null,
    consentedAt: validIso(input.consentedAt),
    completedAt: validIso(input.completedAt),
    updatedAt: validIso(input.updatedAt),
  };
}

function encryptEnvelope(profile, secret) {
  const iv = randomBytes(12);
  const key = deriveKey(secret);
  const cipher = createCipheriv('aes-256-gcm', key, iv);
  const ciphertext = Buffer.concat([cipher.update(JSON.stringify(profile), 'utf8'), cipher.final()]);
  return JSON.stringify({
    version: PROFILE_VERSION,
    algorithm: 'aes-256-gcm',
    iv: iv.toString('base64'),
    tag: cipher.getAuthTag().toString('base64'),
    ciphertext: ciphertext.toString('base64'),
  });
}

function decryptEnvelope(raw, secret) {
  const envelope = JSON.parse(raw);
  if (envelope.algorithm !== 'aes-256-gcm') throw new Error('unsupported encryption envelope');
  const decipher = createDecipheriv('aes-256-gcm', deriveKey(secret), Buffer.from(envelope.iv, 'base64'));
  decipher.setAuthTag(Buffer.from(envelope.tag, 'base64'));
  const plaintext = Buffer.concat([decipher.update(Buffer.from(envelope.ciphertext, 'base64')), decipher.final()]);
  return JSON.parse(plaintext.toString('utf8'));
}

function deriveKey(secret) {
  return createHash('sha256').update(`intelligence-terminal-security-profile\0${secret}`).digest();
}

function clean(value, maxLength) {
  return String(value || '').replace(/[\u0000-\u001f]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, maxLength);
}

function cleanList(value, maxItems, maxLength) {
  const list = Array.isArray(value) ? value : String(value || '').split(',');
  return [...new Set(list.map(item => clean(item, maxLength).toLowerCase()).filter(Boolean))].slice(0, maxItems);
}

function boundedInt(value, min, max, fallback) {
  const parsed = Number.parseInt(value, 10);
  return Number.isFinite(parsed) ? Math.max(min, Math.min(max, parsed)) : fallback;
}

function validIso(value) {
  if (!value) return null;
  const date = new Date(value);
  return Number.isFinite(date.getTime()) ? date.toISOString() : null;
}
