/*
 * Canonical team-hub config at ~/.priority-forge/team-hub.json
 *
 * Unifies pulse emit, memory backend, orchestration opt-in, and salience
 * thresholds. Migrates legacy xmem.json + team-pulse.json on first load.
 */

import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import {
  DEFAULT_MEMORY_HUB_CONFIG,
  MemoryHubBackend,
  MemoryHubConfig,
} from './types';

function resolveConfigDir(): string {
  const override = process.env.PF_HUB_CONFIG_DIR;
  if (override && override.length > 0) return override;
  return path.join(os.homedir(), '.priority-forge');
}

function configDir(): string {
  return resolveConfigDir();
}

function teamHubPath(): string {
  return path.join(configDir(), 'team-hub.json');
}

function legacyXmemPath(): string {
  return path.join(configDir(), 'xmem.json');
}

function legacyTeamPulsePath(): string {
  return path.join(configDir(), 'team-pulse.json');
}

/**
 * @deprecated Resolved at runtime via PF_HUB_CONFIG_DIR. Use teamHubPath() or
 * import getTeamHubConfigPath() if you need the current value.
 */
export const TEAM_HUB_CONFIG_PATH = path.join(
  process.env.PF_HUB_CONFIG_DIR && process.env.PF_HUB_CONFIG_DIR.length > 0
    ? process.env.PF_HUB_CONFIG_DIR
    : path.join(os.homedir(), '.priority-forge'),
  'team-hub.json',
);

export function getTeamHubConfigPath(): string {
  return teamHubPath();
}

let migrationLogged = false;

export interface PulsePrivacyRules {
  defaultVisibility: 'team' | 'private';
  privateProjects: string[];
  privateTasks: string[];
}

export interface PulseConfig {
  emit: boolean;
  userId: string;
  pushImmediately: Array<
    | 'task_selected'
    | 'task_completed'
    | 'task_created'
    | 'task_updated'
    | 'context_switch'
    | 'project_created'
    | 'project_updated'
  >;
  privacyRules: PulsePrivacyRules;
}

export interface OrchestrationConfig {
  enabled: boolean;
}

export interface SalienceThresholds {
  decayTauDays: number;
  interactionWindowDays: number;
  minRetentionDays: number;
  candidateEpsilon: number;
  /** Base strength S0 (ms-scale divisor for retention curve in MVP2). */
  strengthBaseMs: number;
  /** k in S = S0 + k * log(1 + access_count). */
  strengthAccessK: number;
}

export interface TeamHubConfig {
  pulse: PulseConfig;
  memory: MemoryHubConfig;
  orchestration: OrchestrationConfig;
  salience: SalienceThresholds;
}

export const DEFAULT_PULSE_CONFIG: PulseConfig = {
  emit: false,
  userId: '',
  pushImmediately: ['task_selected', 'task_completed'],
  privacyRules: {
    defaultVisibility: 'team',
    privateProjects: [],
    privateTasks: [],
  },
};

export const DEFAULT_SALIENCE_THRESHOLDS: SalienceThresholds = {
  decayTauDays: 14,
  interactionWindowDays: 14,
  minRetentionDays: 90,
  candidateEpsilon: 0.05,
  strengthBaseMs: 14 * 86400 * 1000,
  strengthAccessK: 2 * 86400 * 1000,
};

export const DEFAULT_TEAM_HUB_CONFIG: TeamHubConfig = {
  pulse: DEFAULT_PULSE_CONFIG,
  memory: { ...DEFAULT_MEMORY_HUB_CONFIG },
  orchestration: { enabled: false },
  salience: DEFAULT_SALIENCE_THRESHOLDS,
};

function ensureConfigDir(): void {
  const dir = configDir();
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
}

function memorySliceFromLegacyXmem(raw: Partial<MemoryHubConfig>): MemoryHubConfig {
  return {
    ...DEFAULT_MEMORY_HUB_CONFIG,
    ...raw,
    xmem: { ...DEFAULT_MEMORY_HUB_CONFIG.xmem, ...(raw.xmem ?? {}) },
    pgvector: { ...DEFAULT_MEMORY_HUB_CONFIG.pgvector, ...(raw.pgvector ?? {}) },
  };
}

function pulseSliceFromLegacyTeamPulse(raw: Record<string, unknown>): PulseConfig {
  const privacy = (raw.privacyRules as Partial<PulsePrivacyRules>) ?? {};
  return {
    emit: raw.enabled === true,
    userId: typeof raw.userId === 'string' ? raw.userId : '',
    pushImmediately: Array.isArray(raw.pushImmediately)
      ? (raw.pushImmediately as PulseConfig['pushImmediately'])
      : DEFAULT_PULSE_CONFIG.pushImmediately,
    privacyRules: {
      defaultVisibility:
        privacy.defaultVisibility === 'private' ? 'private' : 'team',
      privateProjects: Array.isArray(privacy.privateProjects)
        ? (privacy.privateProjects as string[])
        : [],
      privateTasks: Array.isArray(privacy.privateTasks)
        ? (privacy.privateTasks as string[])
        : [],
    },
  };
}

function migrateLegacyIfNeeded(): TeamHubConfig | null {
  if (fs.existsSync(teamHubPath())) return null;

  const xmemPath = legacyXmemPath();
  const pulsePath = legacyTeamPulsePath();
  const hasXmem = fs.existsSync(xmemPath);
  const hasPulse = fs.existsSync(pulsePath);
  if (!hasXmem && !hasPulse) return null;

  const next: TeamHubConfig = {
    ...DEFAULT_TEAM_HUB_CONFIG,
    pulse: { ...DEFAULT_PULSE_CONFIG },
    memory: { ...DEFAULT_MEMORY_HUB_CONFIG },
    orchestration: { enabled: false },
    salience: { ...DEFAULT_SALIENCE_THRESHOLDS },
  };

  if (hasXmem) {
    try {
      const raw = JSON.parse(fs.readFileSync(xmemPath, 'utf-8')) as Partial<MemoryHubConfig>;
      next.memory = memorySliceFromLegacyXmem(raw);
      if (next.memory.enabled && next.memory.backend !== 'noop') {
        next.pulse.emit = true;
        if (
          next.memory.backend === 'pgvector' ||
          next.memory.backend === 'dryrun'
        ) {
          next.orchestration.enabled = true;
        }
      }
    } catch {
      /* use defaults */
    }
  }

  if (hasPulse) {
    try {
      const raw = JSON.parse(
        fs.readFileSync(pulsePath, 'utf-8'),
      ) as Record<string, unknown>;
      next.pulse = pulseSliceFromLegacyTeamPulse(raw);
    } catch {
      /* use defaults */
    }
  }

  ensureConfigDir();
  fs.writeFileSync(teamHubPath(), JSON.stringify(next, null, 2), 'utf-8');
  if (!migrationLogged) {
    console.warn(
      '[team-hub] migrated legacy xmem.json / team-pulse.json → team-hub.json',
    );
    migrationLogged = true;
  }
  return next;
}

function mergeTeamHub(parsed: Partial<TeamHubConfig>): TeamHubConfig {
  return {
    pulse: {
      ...DEFAULT_PULSE_CONFIG,
      ...(parsed.pulse ?? {}),
      privacyRules: {
        ...DEFAULT_PULSE_CONFIG.privacyRules,
        ...(parsed.pulse?.privacyRules ?? {}),
      },
    },
    memory: memorySliceFromLegacyXmem(parsed.memory ?? {}),
    orchestration: {
      ...DEFAULT_TEAM_HUB_CONFIG.orchestration,
      ...(parsed.orchestration ?? {}),
    },
    salience: {
      ...DEFAULT_SALIENCE_THRESHOLDS,
      ...(parsed.salience ?? {}),
    },
  };
}

export function loadTeamHubConfig(): TeamHubConfig {
  try {
    const migrated = migrateLegacyIfNeeded();
    if (migrated) return migrated;

    const p = teamHubPath();
    if (!fs.existsSync(p)) {
      return {
        ...DEFAULT_TEAM_HUB_CONFIG,
        memory: { ...DEFAULT_MEMORY_HUB_CONFIG },
        pulse: { ...DEFAULT_PULSE_CONFIG },
      };
    }
    const raw = fs.readFileSync(p, 'utf-8');
    const parsed = JSON.parse(raw) as Partial<TeamHubConfig>;
    return mergeTeamHub(parsed);
  } catch (err) {
    console.error('[team-hub] failed to load config, using defaults:', err);
    return {
      ...DEFAULT_TEAM_HUB_CONFIG,
      memory: { ...DEFAULT_MEMORY_HUB_CONFIG },
      pulse: { ...DEFAULT_PULSE_CONFIG },
    };
  }
}

export function saveTeamHubConfig(config: TeamHubConfig): void {
  ensureConfigDir();
  fs.writeFileSync(teamHubPath(), JSON.stringify(config, null, 2), 'utf-8');
}

export function updateTeamHubConfig(patch: Partial<TeamHubConfig>): TeamHubConfig {
  const current = loadTeamHubConfig();
  const next = mergeTeamHub({
    pulse: patch.pulse ? { ...current.pulse, ...patch.pulse } : current.pulse,
    memory: patch.memory
      ? {
          ...current.memory,
          ...patch.memory,
          xmem: { ...current.memory.xmem, ...(patch.memory.xmem ?? {}) },
          pgvector: {
            ...current.memory.pgvector,
            ...(patch.memory.pgvector ?? {}),
          },
        }
      : current.memory,
    orchestration: patch.orchestration
      ? { ...current.orchestration, ...patch.orchestration }
      : current.orchestration,
    salience: patch.salience ? { ...current.salience, ...patch.salience } : current.salience,
  });
  saveTeamHubConfig(next);
  return next;
}

/** @deprecated Use getTeamHubConfigPath() */
export const MEMORY_HUB_CONFIG_PATH = TEAM_HUB_CONFIG_PATH;

export function loadMemoryHubConfig(): MemoryHubConfig {
  return loadTeamHubConfig().memory;
}

export function saveMemoryHubConfig(config: MemoryHubConfig): void {
  const hub = loadTeamHubConfig();
  hub.memory = memorySliceFromLegacyXmem(config);
  saveTeamHubConfig(hub);
}

export function updateMemoryHubConfig(patch: Partial<MemoryHubConfig>): MemoryHubConfig {
  const hub = loadTeamHubConfig();
  hub.memory = memorySliceFromLegacyXmem({ ...hub.memory, ...patch });
  saveTeamHubConfig(hub);
  return hub.memory;
}

export function loadPulseConfig(): PulseConfig {
  return loadTeamHubConfig().pulse;
}

export function updatePulseConfig(patch: Partial<PulseConfig>): PulseConfig {
  const hub = loadTeamHubConfig();
  hub.pulse = {
    ...hub.pulse,
    ...patch,
    privacyRules: {
      ...hub.pulse.privacyRules,
      ...(patch.privacyRules ?? {}),
    },
  };
  saveTeamHubConfig(hub);
  return hub.pulse;
}

export function isMemoryBackendActive(backend: MemoryHubBackend): boolean {
  return backend !== 'noop';
}

/** True when subscriber should call MemoryHub.write (not noop / disabled / force noop). */
export function isMemoryPersistenceActive(hub?: TeamHubConfig): boolean {
  const h = hub ?? loadTeamHubConfig();
  if (process.env.XMEM_FORCE_NOOP === '1') return false;
  return h.memory.enabled && isMemoryBackendActive(h.memory.backend);
}

const ORCHESTRATION_BACKENDS: MemoryHubBackend[] = ['pgvector', 'dryrun'];

export function validateHubConfig(hub: TeamHubConfig): void {
  if (hub.memory.enabled && hub.memory.backend === 'noop') {
    throw new Error(
      'memory.enabled cannot be true with backend=noop — disable memory or pick pgvector/dryrun/xmem',
    );
  }
  if (hub.orchestration.enabled) {
    if (!hub.memory.enabled) {
      throw new Error('orchestration.enabled requires memory.enabled');
    }
    if (!ORCHESTRATION_BACKENDS.includes(hub.memory.backend)) {
      throw new Error(
        `orchestration.enabled requires backend pgvector or dryrun (got ${hub.memory.backend})`,
      );
    }
  }
}

export function applyHubConfigureParams(
  params: Record<string, unknown>,
): TeamHubConfig {
  const current = loadTeamHubConfig();
  const patch: Partial<TeamHubConfig> = {};

  const pulseRaw = params.pulse as Record<string, unknown> | undefined;
  if (pulseRaw) {
    patch.pulse = {
      ...current.pulse,
      ...(pulseRaw.emit !== undefined ? { emit: pulseRaw.emit === true } : {}),
      ...(typeof pulseRaw.userId === 'string' ? { userId: pulseRaw.userId } : {}),
      ...(Array.isArray(pulseRaw.pushImmediately)
        ? { pushImmediately: pulseRaw.pushImmediately as PulseConfig['pushImmediately'] }
        : {}),
      privacyRules: {
        ...current.pulse.privacyRules,
        ...((pulseRaw.privacyRules as Partial<PulsePrivacyRules>) ?? {}),
      },
    };
  }

  const memoryRaw = params.memory as Record<string, unknown> | undefined;
  if (memoryRaw) {
    const memPatch: Partial<MemoryHubConfig> = {
      ...(memoryRaw.enabled !== undefined ? { enabled: memoryRaw.enabled === true } : {}),
      ...(typeof memoryRaw.backend === 'string'
        ? { backend: memoryRaw.backend as MemoryHubBackend }
        : {}),
      ...(typeof memoryRaw.teamId === 'string' ? { teamId: memoryRaw.teamId } : {}),
      ...(typeof memoryRaw.agentId === 'string' ? { agentId: memoryRaw.agentId } : {}),
      ...(typeof memoryRaw.syncIntervalMs === 'number'
        ? { syncIntervalMs: memoryRaw.syncIntervalMs }
        : {}),
    };
    const xmemRaw = memoryRaw.xmem as Record<string, unknown> | undefined;
    if (xmemRaw) {
      memPatch.xmem = {
        ...current.memory.xmem,
        ...(typeof xmemRaw.baseUrl === 'string' ? { baseUrl: xmemRaw.baseUrl } : {}),
        ...(typeof xmemRaw.apiKey === 'string' ? { apiKey: xmemRaw.apiKey } : {}),
        ...(typeof xmemRaw.orgID === 'string' ? { orgID: xmemRaw.orgID } : {}),
        ...(xmemRaw.environment === 'staging' || xmemRaw.environment === 'production'
          ? { environment: xmemRaw.environment }
          : {}),
      };
    }
    const pgRaw = memoryRaw.pgvector as Record<string, unknown> | undefined;
    if (pgRaw) {
      memPatch.pgvector = {
        ...current.memory.pgvector,
        ...(typeof pgRaw.connectionString === 'string'
          ? { connectionString: pgRaw.connectionString }
          : {}),
        ...(typeof pgRaw.agentToken === 'string' ? { agentToken: pgRaw.agentToken } : {}),
      };
    }
    patch.memory = { ...current.memory, ...memPatch };
  }

  const orchRaw = params.orchestration as Record<string, unknown> | undefined;
  if (orchRaw) {
    patch.orchestration = {
      ...current.orchestration,
      ...(orchRaw.enabled !== undefined ? { enabled: orchRaw.enabled === true } : {}),
    };
  }

  const salienceRaw = params.salience as Partial<SalienceThresholds> | undefined;
  if (salienceRaw) {
    patch.salience = { ...current.salience, ...salienceRaw };
  }

  let next = mergeTeamHub({
    pulse: patch.pulse ?? current.pulse,
    memory: patch.memory ?? current.memory,
    orchestration: patch.orchestration ?? current.orchestration,
    salience: patch.salience ?? current.salience,
  });

  if (
    next.memory.enabled &&
    isMemoryBackendActive(next.memory.backend) &&
    !pulseRaw
  ) {
    next = { ...next, pulse: { ...next.pulse, emit: true } };
  }

  if (
    next.orchestration.enabled &&
    ORCHESTRATION_BACKENDS.includes(next.memory.backend) &&
    !orchRaw
  ) {
    /* orchestration explicitly requested via memory-only enable paths */
  }

  validateHubConfig(next);
  saveTeamHubConfig(next);
  return next;
}

export function collectHubStatusWarnings(hub: TeamHubConfig): string[] {
  const warnings: string[] = [];
  if (process.env.XMEM_FORCE_NOOP === '1') {
    warnings.push(
      'XMEM_FORCE_NOOP=1 forces noop adapter regardless of team-hub.json memory.backend',
    );
  }
  if (hub.memory.enabled && hub.memory.backend === 'noop') {
    warnings.push('memory.enabled is true but backend is noop — no events will persist');
  }
  if (hub.memory.enabled && isMemoryBackendActive(hub.memory.backend) && !hub.pulse.emit) {
    warnings.push('memory is enabled but pulse.emit is false — enable pulse or hub_configure memory');
  }
  if (hub.orchestration.enabled && !hub.memory.enabled) {
    warnings.push('orchestration.enabled requires memory.enabled');
  }
  if (
    hub.orchestration.enabled &&
    hub.memory.enabled &&
    hub.memory.backend !== 'pgvector' &&
    hub.memory.backend !== 'dryrun'
  ) {
    warnings.push(
      `orchestration.enabled but backend=${hub.memory.backend} does not support shared_task CAS`,
    );
  }
  return warnings;
}
