/**
 * Trust Dynamics Engine — computes trust scores from delegation graphs.
 *
 * Algorithm:
 * 1. Direct weight from active delegations
 * 2. Time-decayed based on last trust event (half-life = 30 days)
 * 3. Transitive trust via BFS through delegation chains (50% discount per hop)
 * 4. Floor: trust never reaches zero (min 0.01)
 * 5. Composite: clamp(direct + transitive, 0.01, 1.0)
 *
 * Runs as a background job every 5 minutes (same pattern as cleanExpiredChallenges).
 */

import {
  listActiveDelegations,
  getTrustEventsSince,
  upsertTrustScore,
  cleanExpiredDelegations,
  logTrustEvent,
  type StoredDelegation,
  type DelegationAuthority,
} from './db.js';
import { computeSpacePowerIndices } from './power-indices.js';

// ── Configuration ──

export const TRUST_CONFIG = {
  growthRate: 0.05,           // 5% per positive event
  decayRate: 0.10,            // 10% per negative event (2x growth)
  timeDecayHalfLife: 30,      // days — trust halves with no activity
  minTrust: 0.01,             // trust never reaches zero
  maxChainDepth: 3,           // default transitive delegation limit
  transitiveDiscount: 0.5,    // each hop reduces trust 50%
  recomputeIntervalMs: 5 * 60 * 1000, // 5 minutes
};

// ── Types ──

interface DelegationEdge {
  delegatorDid: string;
  delegateDid: string;
  weight: number;
  maxDepth: number;
}

interface ComputedScore {
  sourceDid: string;
  targetDid: string;
  authority: string;
  spaceSlug: string;
  directWeight: number;
  transitiveWeight: number;
  score: number;
}

// ── Core Computation ──

/**
 * Compute trust scores for all delegation relationships in a space+authority.
 * Returns scores for every (source → target) pair that has nonzero trust.
 */
export function computeTrustScores(
  delegations: StoredDelegation[],
  authority: string,
  spaceSlug: string,
  lastEventTimes?: Map<string, number>, // key: "source:target" → timestamp
): ComputedScore[] {
  const now = Date.now();
  const halfLifeMs = TRUST_CONFIG.timeDecayHalfLife * 24 * 60 * 60 * 1000;

  // Build adjacency list: delegator → [{ delegate, weight, maxDepth }]
  const edges = new Map<string, DelegationEdge[]>();
  for (const d of delegations) {
    if (d.authority !== authority) continue;
    const list = edges.get(d.delegatorDid) || [];
    list.push({
      delegatorDid: d.delegatorDid,
      delegateDid: d.delegateDid,
      weight: d.weight,
      maxDepth: d.maxDepth,
    });
    edges.set(d.delegatorDid, list);
  }

  const scores: ComputedScore[] = [];

  // For each delegator, compute direct + transitive trust to all reachable delegates
  edges.forEach((directEdges, delegator) => {
    // BFS through delegation chains from this delegator
    const visited = new Set<string>([delegator]);
    // queue: [targetDid, accumulatedWeight, hopsRemaining]
    const queue: Array<[string, number, number]> = [];

    // Seed with direct delegations
    for (const edge of directEdges) {
      if (visited.has(edge.delegateDid)) continue;

      // Apply time decay to direct weight
      const eventKey = `${delegator}:${edge.delegateDid}`;
      const lastEvent = lastEventTimes?.get(eventKey) ?? now;
      const daysSince = (now - lastEvent) / (24 * 60 * 60 * 1000);
      const decayFactor = Math.pow(0.5, daysSince / TRUST_CONFIG.timeDecayHalfLife);
      const decayedWeight = Math.max(TRUST_CONFIG.minTrust, edge.weight * decayFactor);

      visited.add(edge.delegateDid);

      // Record direct score
      const directScore: ComputedScore = {
        sourceDid: delegator,
        targetDid: edge.delegateDid,
        authority,
        spaceSlug,
        directWeight: decayedWeight,
        transitiveWeight: 0,
        score: Math.max(TRUST_CONFIG.minTrust, Math.min(1, decayedWeight)),
      };
      scores.push(directScore);

      // Enqueue for transitive exploration
      if (edge.maxDepth > 1) {
        queue.push([edge.delegateDid, decayedWeight, edge.maxDepth - 1]);
      }
    }

    // BFS for transitive trust
    while (queue.length > 0) {
      const [currentDid, parentWeight, remainingDepth] = queue.shift()!;
      const transitiveEdges = edges.get(currentDid);
      if (!transitiveEdges || remainingDepth <= 0) continue;

      for (const edge of transitiveEdges) {
        if (visited.has(edge.delegateDid)) continue;
        visited.add(edge.delegateDid);

        const transitiveWeight = parentWeight * edge.weight * TRUST_CONFIG.transitiveDiscount;
        if (transitiveWeight < TRUST_CONFIG.minTrust * 0.1) continue; // prune tiny values

        // Check if we already have a direct score for this target
        const existing = scores.find(
          s => s.sourceDid === delegator && s.targetDid === edge.delegateDid && s.authority === authority
        );

        if (existing) {
          existing.transitiveWeight += transitiveWeight;
          existing.score = Math.max(TRUST_CONFIG.minTrust, Math.min(1, existing.directWeight + existing.transitiveWeight));
        } else {
          scores.push({
            sourceDid: delegator,
            targetDid: edge.delegateDid,
            authority,
            spaceSlug,
            directWeight: 0,
            transitiveWeight,
            score: Math.max(TRUST_CONFIG.minTrust, Math.min(1, transitiveWeight)),
          });
        }

        // Continue BFS if depth allows
        if (remainingDepth > 1) {
          queue.push([edge.delegateDid, transitiveWeight, remainingDepth - 1]);
        }
      }
    }
  });

  return scores;
}

// ── Background Recomputation ──

const AUTHORITIES: DelegationAuthority[] = ['gov-ops', 'fin-ops', 'dev-ops', 'custom'];

/**
 * Recompute all trust scores for a single space.
 * Called by the background job for each space that has delegations.
 */
export async function recomputeSpaceTrustScores(spaceSlug: string): Promise<number> {
  let totalScores = 0;

  for (const authority of AUTHORITIES) {
    const delegations = await listActiveDelegations(spaceSlug, authority);
    if (delegations.length === 0) continue;

    // Build last-event-time map from recent events (last 90 days)
    const since = Date.now() - 90 * 24 * 60 * 60 * 1000;
    const events = await getTrustEventsSince(spaceSlug, since);
    const lastEventTimes = new Map<string, number>();
    for (const evt of events) {
      const key = `${evt.sourceDid}:${evt.targetDid}`;
      const existing = lastEventTimes.get(key) || 0;
      if (evt.createdAt > existing) lastEventTimes.set(key, evt.createdAt);
    }

    const scores = computeTrustScores(delegations, authority, spaceSlug, lastEventTimes);

    // Upsert all computed scores
    for (const s of scores) {
      await upsertTrustScore(s);
    }
    totalScores += scores.length;
  }

  // Recompute power indices (Banzhaf, Shapley-Shubik) from updated trust scores
  try {
    const piCount = await computeSpacePowerIndices(spaceSlug);
    if (piCount > 0) {
      console.log(`[trust-engine] Recomputed ${piCount} power indices for space "${spaceSlug}"`);
    }
  } catch (err) {
    console.error(`[trust-engine] Power index computation failed for "${spaceSlug}":`, (err as Error).message);
  }

  return totalScores;
}

/**
 * Recompute trust scores across all spaces that have active delegations.
 * This is the main entry point for the background job.
 */
export async function recomputeAllTrustScores(): Promise<void> {
  try {
    // Get distinct space slugs from active delegations
    const allDelegations = await listActiveDelegations('__all__'); // Won't match anything
    // Instead, query distinct spaces directly
    const { sql } = await import('./db.js');
    const spaces = await sql`
      SELECT DISTINCT space_slug FROM delegations WHERE state = 'active'
    `;

    for (const { space_slug } of spaces) {
      try {
        const count = await recomputeSpaceTrustScores(space_slug);
        if (count > 0) {
          console.log(`[trust-engine] Recomputed ${count} scores for space "${space_slug}"`);
        }
      } catch (err) {
        console.error(`[trust-engine] Error recomputing space "${space_slug}":`, (err as Error).message);
      }
    }
  } catch (err) {
    console.error('[trust-engine] Recomputation failed:', (err as Error).message);
  }
}

// ── Background Job ──

let recomputeInterval: ReturnType<typeof setInterval> | null = null;

export function startTrustEngine(): void {
  if (recomputeInterval) return;
  console.log('[trust-engine] Starting background recomputation (every 5 min)');

  recomputeInterval = setInterval(async () => {
    await cleanExpiredDelegations();
    await recomputeAllTrustScores();
  }, TRUST_CONFIG.recomputeIntervalMs);

  // Run once on startup (delayed 30s to let DB stabilize)
  setTimeout(async () => {
    await cleanExpiredDelegations();
    await recomputeAllTrustScores();
  }, 30_000);
}

export function stopTrustEngine(): void {
  if (recomputeInterval) {
    clearInterval(recomputeInterval);
    recomputeInterval = null;
  }
}
