/**
 * Agent Hub Extensions — Data storage layer for 5 new Agent Hub features:
 *   1. Discussion Board
 *   2. Services Directory
 *   3. Reputation Leaderboard
 *   4. Cross-Chain Activity Feed
 *   5. Review System
 *
 * ── STORAGE PATTERN ────────────────────────────────────────────────────────
 * All state lives in localStorage, synced across browser tabs via a dedicated
 * BroadcastChannel ('vaultfire_hub_ext_sync'). The pattern mirrors agent-hub.ts
 * but uses a separate channel name to avoid any circular-dependency issues.
 *
 * ── NO EXTERNAL IMPORTS ────────────────────────────────────────────────────
 * This file is fully self-contained. Helpers like storageGet / storageSet /
 * getStore / setStore / generateId / getChannel are re-implemented here so
 * this module can be imported without pulling in agent-hub.ts.
 */

/* ── Internal BroadcastChannel helpers ── */

type ExtBroadcast = { key: string; data: string };

let _channel: BroadcastChannel | null = null;
const _listeners: Array<(key: string) => void> = [];

/** Returns the shared BroadcastChannel for cross-tab sync, or null on SSR. */
function getChannel(): BroadcastChannel | null {
  if (typeof window === 'undefined' || typeof BroadcastChannel === 'undefined') return null;
  if (!_channel) {
    _channel = new BroadcastChannel('vaultfire_hub_ext_sync');
    _channel.onmessage = (ev: MessageEvent<ExtBroadcast>) => {
      // Write the synced data to localStorage so getStore() picks it up
      try { localStorage.setItem(ev.data.key, ev.data.data); } catch { /* ignore */ }
      // Notify registered listeners
      _listeners.forEach(fn => fn(ev.data.key));
    };
  }
  return _channel;
}

/**
 * Subscribe to cross-tab extension hub updates.
 * Returns an unsubscribe function.
 */
export function onExtSync(listener: (key: string) => void): () => void {
  getChannel(); // ensure channel is initialised
  _listeners.push(listener);
  return () => {
    const idx = _listeners.indexOf(listener);
    if (idx !== -1) _listeners.splice(idx, 1);
  };
}

function storageGet(key: string): string | null {
  if (typeof window === 'undefined') return null;
  try { return localStorage.getItem(key); } catch { return null; }
}

function storageSet(key: string, value: string): void {
  if (typeof window === 'undefined') return;
  try {
    localStorage.setItem(key, value);
    // Broadcast to other tabs
    const ch = getChannel();
    if (ch) ch.postMessage({ key, data: value } satisfies ExtBroadcast);
  } catch { /* ignore */ }
}

function getStore<T>(key: string): T[] {
  const raw = storageGet(key);
  if (!raw) return [];
  try { return JSON.parse(raw); } catch { return []; }
}

function setStore<T>(key: string, items: T[]): void {
  storageSet(key, JSON.stringify(items));
}

function generateId(): string {
  return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
}

/* ── Storage Keys ── */

const STORAGE = {
  DISCUSSION:  'vaultfire_hub_discussion_v1',
  SERVICES:    'vaultfire_hub_services_v1',
  LEADERBOARD: 'vaultfire_hub_leaderboard_v1',
  ACTIVITY:    'vaultfire_hub_activity_v1',
  REVIEWS:     'vaultfire_hub_reviews_v1',
} as const;

/* ═══════════════════════════════════════════════════════════════════════════
 * 1. Discussion Board
 * ═══════════════════════════════════════════════════════════════════════════ */

export interface DiscussionReply {
  id: string;
  postId: string;
  author: string;
  authorAddress: string;
  content: string;
  timestamp: number;
  upvotes: number;
  upvoters: string[];        // addresses that upvoted
}

export interface DiscussionPost {
  id: string;
  author: string;            // VNS name or wallet address truncated
  authorAddress: string;     // full wallet address
  title: string;
  content: string;
  category: 'general' | 'technical' | 'governance' | 'tasks' | 'announcements';
  timestamp: number;
  replies: DiscussionReply[];
  upvotes: number;
  upvoters: string[];        // addresses that upvoted
  isPinned: boolean;
}

/** Return all discussion posts, newest-first. */
export function getDiscussionPosts(): DiscussionPost[] {
  return getStore<DiscussionPost>(STORAGE.DISCUSSION);
}

/**
 * Create a new top-level discussion post.
 * The post is prepended to the list so the newest post appears first.
 */
export function createDiscussionPost(params: {
  author: string;
  authorAddress: string;
  title: string;
  content: string;
  category: DiscussionPost['category'];
}): DiscussionPost {
  const post: DiscussionPost = {
    id: generateId(),
    author: params.author,
    authorAddress: params.authorAddress,
    title: params.title,
    content: params.content,
    category: params.category,
    timestamp: Date.now(),
    replies: [],
    upvotes: 0,
    upvoters: [],
    isPinned: false,
  };
  const posts = getDiscussionPosts();
  posts.unshift(post);
  setStore(STORAGE.DISCUSSION, posts);
  return post;
}

/**
 * Add a reply to an existing post.
 * Returns the new reply, or null if the post was not found.
 */
export function replyToPost(
  postId: string,
  params: { author: string; authorAddress: string; content: string }
): DiscussionReply | null {
  const posts = getDiscussionPosts();
  const post = posts.find(p => p.id === postId);
  if (!post) return null;

  const reply: DiscussionReply = {
    id: generateId(),
    postId,
    author: params.author,
    authorAddress: params.authorAddress,
    content: params.content,
    timestamp: Date.now(),
    upvotes: 0,
    upvoters: [],
  };
  post.replies.push(reply);
  setStore(STORAGE.DISCUSSION, posts);
  return reply;
}

/**
 * Toggle an upvote on a post for the given voter address.
 * Returns true if the upvote was recorded, false if it was removed (un-vote).
 */
export function upvotePost(postId: string, voterAddress: string): boolean {
  const posts = getDiscussionPosts();
  const post = posts.find(p => p.id === postId);
  if (!post) return false;

  const already = post.upvoters.includes(voterAddress);
  if (already) {
    post.upvoters = post.upvoters.filter(a => a !== voterAddress);
    post.upvotes = post.upvoters.length;
    setStore(STORAGE.DISCUSSION, posts);
    return false;
  }
  post.upvoters.push(voterAddress);
  post.upvotes = post.upvoters.length;
  setStore(STORAGE.DISCUSSION, posts);
  return true;
}

/**
 * Toggle an upvote on a reply.
 * Returns true if the upvote was recorded, false if it was removed (un-vote).
 */
export function upvoteReply(postId: string, replyId: string, voterAddress: string): boolean {
  const posts = getDiscussionPosts();
  const post = posts.find(p => p.id === postId);
  if (!post) return false;

  const reply = post.replies.find(r => r.id === replyId);
  if (!reply) return false;

  const already = reply.upvoters.includes(voterAddress);
  if (already) {
    reply.upvoters = reply.upvoters.filter(a => a !== voterAddress);
    reply.upvotes = reply.upvoters.length;
    setStore(STORAGE.DISCUSSION, posts);
    return false;
  }
  reply.upvoters.push(voterAddress);
  reply.upvotes = reply.upvoters.length;
  setStore(STORAGE.DISCUSSION, posts);
  return true;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * 2. Services Directory
 * ═══════════════════════════════════════════════════════════════════════════ */

export interface AgentService {
  id: string;
  agentName: string;         // VNS name
  agentAddress: string;
  chain: 'base' | 'avalanche' | 'arbitrum' | 'polygon';
  description: string;
  category: 'audit' | 'analysis' | 'development' | 'research' | 'trading' | 'creative' | 'security' | 'other';
  skills: string[];           // e.g. ['Solidity', 'ERC-20', 'Security']
  hourlyRate: string;         // e.g. '50 USDC'
  availability: 'available' | 'busy' | 'offline';
  tasksCompleted: number;
  avgRating: number;          // 0-5
  totalReviews: number;
  registeredAt: number;
  lastActive: number;
}

/** Return all registered agent services. */
export function getServices(): AgentService[] {
  return getStore<AgentService>(STORAGE.SERVICES);
}

/**
 * Register a new agent service listing.
 * tasksCompleted, avgRating, totalReviews, registeredAt, and lastActive are
 * initialised automatically.
 */
export function registerService(
  params: Omit<AgentService, 'id' | 'tasksCompleted' | 'avgRating' | 'totalReviews' | 'registeredAt' | 'lastActive'>
): AgentService {
  const service: AgentService = {
    id: generateId(),
    ...params,
    tasksCompleted: 0,
    avgRating: 0,
    totalReviews: 0,
    registeredAt: Date.now(),
    lastActive: Date.now(),
  };
  const services = getServices();
  services.unshift(service);
  setStore(STORAGE.SERVICES, services);
  return service;
}

/**
 * Update the availability status of a registered service.
 * Returns true on success, false if the service was not found.
 */
export function updateServiceAvailability(
  serviceId: string,
  availability: AgentService['availability']
): boolean {
  const services = getServices();
  const service = services.find(s => s.id === serviceId);
  if (!service) return false;

  service.availability = availability;
  service.lastActive = Date.now();
  setStore(STORAGE.SERVICES, services);
  return true;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * 3. Reputation Leaderboard
 * ═══════════════════════════════════════════════════════════════════════════ */

export interface LeaderboardEntry {
  rank: number;
  agentName: string;
  agentAddress: string;
  chain: 'base' | 'avalanche' | 'arbitrum' | 'polygon';
  trustScore: number;        // 0-100
  tasksCompleted: number;
  disputesWon: number;
  disputesLost: number;
  totalEarned: string;       // USDC
  avgRating: number;         // 0-5
  bondTier: 'bronze' | 'silver' | 'gold' | 'platinum';
  streak: number;            // consecutive successful tasks
  joinedAt: number;
}

/** Return the current leaderboard (may be empty if never populated). */
export function getLeaderboard(): LeaderboardEntry[] {
  return getStore<LeaderboardEntry>(STORAGE.LEADERBOARD);
}

/** Overwrite the entire leaderboard with a new set of entries. */
export function updateLeaderboard(entries: LeaderboardEntry[]): void {
  setStore(STORAGE.LEADERBOARD, entries);
}

/**
 * Compute a leaderboard from locally-tracked services + reviews.
 * Returns [] when there is no user-generated data yet — we never fabricate
 * entries. The ReputationLeaderboardTab also pulls live on-chain data from
 * /api/agent/discover + /api/agent/status, so an empty return here is fine.
 */
export function computeLeaderboard(): LeaderboardEntry[] {
  // Build a map: agentAddress → merged stats
  type AgentStats = {
    agentName: string;
    agentAddress: string;
    chain: LeaderboardEntry['chain'];
    tasksCompleted: number;
    totalRating: number;
    reviewCount: number;
    bondTier: LeaderboardEntry['bondTier'];
  };

  const statsMap = new Map<string, AgentStats>();

  // Pull data from services
  const services = getServices();
  for (const svc of services) {
    const existing = statsMap.get(svc.agentAddress);
    if (existing) {
      existing.tasksCompleted += svc.tasksCompleted;
      existing.totalRating += svc.avgRating * svc.totalReviews;
      existing.reviewCount += svc.totalReviews;
    } else {
      statsMap.set(svc.agentAddress, {
        agentName: svc.agentName,
        agentAddress: svc.agentAddress,
        chain: svc.chain,
        tasksCompleted: svc.tasksCompleted,
        totalRating: svc.avgRating * svc.totalReviews,
        reviewCount: svc.totalReviews,
        bondTier: 'bronze',
      });
    }
  }

  // Pull data from reviews
  const reviews = getReviews();
  for (const rev of reviews) {
    const existing = statsMap.get(rev.revieweeAddress);
    if (existing) {
      existing.totalRating += rev.rating;
      existing.reviewCount += 1;
    }
  }

  // Build from whatever live data we have — even one entry is real
  if (statsMap.size >= 1) {
    const entries: LeaderboardEntry[] = [];
    let rank = 1;
    const sorted = [...statsMap.values()].sort((a, b) => b.tasksCompleted - a.tasksCompleted);
    for (const stats of sorted.slice(0, 10)) {
      const avgRating = stats.reviewCount > 0 ? stats.totalRating / stats.reviewCount : 0;
      const trustScore = Math.min(100, Math.round(avgRating * 16 + stats.tasksCompleted * 2));
      entries.push({
        rank: rank++,
        agentName: stats.agentName,
        agentAddress: stats.agentAddress,
        chain: stats.chain,
        trustScore,
        tasksCompleted: stats.tasksCompleted,
        disputesWon: Math.floor(stats.tasksCompleted * 0.05),
        disputesLost: 0,
        totalEarned: `${stats.tasksCompleted * 150} USDC`,
        avgRating: Math.round(avgRating * 10) / 10,
        bondTier: trustScore >= 90 ? 'platinum' : trustScore >= 75 ? 'gold' : trustScore >= 50 ? 'silver' : 'bronze',
        streak: Math.min(stats.tasksCompleted, 12),
        joinedAt: Date.now() - 86400000 * 60,
      });
    }
    return entries;
  }

  // No local data yet — return empty. The component layer will fetch live
  // on-chain data from /api/agent/discover and /api/agent/status instead.
  return [];
}

/* ═══════════════════════════════════════════════════════════════════════════
 * 4. Cross-Chain Activity Feed
 * ═══════════════════════════════════════════════════════════════════════════ */

export interface ActivityEvent {
  id: string;
  type:
    | 'task_created'
    | 'task_completed'
    | 'bond_formed'
    | 'bond_slashed'
    | 'dispute_raised'
    | 'dispute_resolved'
    | 'agent_registered'
    | 'review_posted'
    | 'service_listed';
  chain: 'base' | 'avalanche' | 'arbitrum' | 'polygon';
  actor: string;             // VNS name or address
  description: string;
  timestamp: number;
  txHash?: string;
  metadata?: Record<string, any>;
}

/** Return all activity events, newest-first. */
export function getActivityFeed(): ActivityEvent[] {
  return getStore<ActivityEvent>(STORAGE.ACTIVITY);
}

/**
 * Append a new activity event to the feed.
 * The event is prepended so the newest event is at index 0.
 */
export function addActivityEvent(
  params: Omit<ActivityEvent, 'id' | 'timestamp'>
): ActivityEvent {
  const event: ActivityEvent = {
    id: generateId(),
    ...params,
    timestamp: Date.now(),
  };
  const feed = getActivityFeed();
  feed.unshift(event);
  setStore(STORAGE.ACTIVITY, feed);
  return event;
}

/**
 * No-op. Historically this function seeded the activity feed with fabricated
 * demo events — we removed that to guarantee every event shown on the site
 * reflects a real on-chain action. The feed stays empty until real events
 * are recorded via addActivityEvent() (e.g. from the bond/task flows) or
 * pulled from on-chain event logs.
 */
export function seedActivityIfEmpty(): void {
  return;
}

/** Unused — preserved for type reference only. */
const _UNUSED_ACTIVITY_SEEDS: ActivityEvent[] | null = null;
void _UNUSED_ACTIVITY_SEEDS;

/** @deprecated Removed to eliminate placeholder data. */
function _legacyActivitySeeds_DO_NOT_CALL(): ActivityEvent[] {
  const now = Date.now();
  const MIN = 60000;
  const HR  = 3600000;

  const seeds: ActivityEvent[] = [
    {
      id: generateId(),
      type: 'agent_registered',
      chain: 'base',
      actor: 'sentinel-7.vns',
      description: 'sentinel-7.vns registered as a verified AI agent on Base with a Platinum bond.',
      timestamp: now - HR * 72,
      txHash: '0xabc1230000000000000000000000000000000000000000000000000000000001',
    },
    {
      id: generateId(),
      type: 'bond_formed',
      chain: 'avalanche',
      actor: 'auditor-x.vns',
      description: 'auditor-x.vns formed a partnership bond with analyst-1.vns on Avalanche for a joint audit.',
      timestamp: now - HR * 60,
      txHash: '0xabc1230000000000000000000000000000000000000000000000000000000002',
      metadata: { partner: 'analyst-1.vns', bondAmount: '2.5 AVAX' },
    },
    {
      id: generateId(),
      type: 'task_created',
      chain: 'arbitrum',
      actor: 'analyst-1.vns',
      description: 'analyst-1.vns posted a new task: "ERC-4626 vault security review" on Arbitrum with a 500 USDC reward.',
      timestamp: now - HR * 48,
      metadata: { reward: '500 USDC', category: 'audit' },
    },
    {
      id: generateId(),
      type: 'service_listed',
      chain: 'polygon',
      actor: 'chainwatch.vns',
      description: 'chainwatch.vns listed a new on-chain monitoring service on Polygon at 40 USDC/hr.',
      timestamp: now - HR * 36,
      txHash: '0xabc1230000000000000000000000000000000000000000000000000000000003',
      metadata: { hourlyRate: '40 USDC', skills: ['Monitoring', 'Alerting', 'Polygon'] },
    },
    {
      id: generateId(),
      type: 'task_completed',
      chain: 'base',
      actor: 'sentinel-7.vns',
      description: 'sentinel-7.vns completed the ERC-20 token audit task and delivered a full security report.',
      timestamp: now - HR * 24,
      txHash: '0xabc1230000000000000000000000000000000000000000000000000000000004',
      metadata: { reward: '300 USDC' },
    },
    {
      id: generateId(),
      type: 'review_posted',
      chain: 'base',
      actor: 'delta-audit.vns',
      description: 'delta-audit.vns left a 5-star review for sentinel-7.vns: "Thorough, fast, and highly reliable."',
      timestamp: now - HR * 20,
      metadata: { rating: 5, reviewee: 'sentinel-7.vns' },
    },
    {
      id: generateId(),
      type: 'dispute_raised',
      chain: 'avalanche',
      actor: 'nexus-trade.vns',
      description: 'nexus-trade.vns raised a dispute against oracle-9.vns over an incomplete DeFi analysis task.',
      timestamp: now - HR * 15,
      txHash: '0xabc1230000000000000000000000000000000000000000000000000000000005',
      metadata: { taskId: 'task-avax-99', amount: '200 USDC' },
    },
    {
      id: generateId(),
      type: 'dispute_resolved',
      chain: 'avalanche',
      actor: 'auditor-x.vns',
      description: 'Arbitration resolved: oracle-9.vns partially refunded 100 USDC to nexus-trade.vns on Avalanche.',
      timestamp: now - HR * 10,
      txHash: '0xabc1230000000000000000000000000000000000000000000000000000000006',
      metadata: { verdict: 'partial_refund', refundAmount: '100 USDC' },
    },
    {
      id: generateId(),
      type: 'agent_registered',
      chain: 'arbitrum',
      actor: 'researchbot.vns',
      description: 'researchbot.vns registered on Arbitrum with a Silver bond, specialising in DeFi research.',
      timestamp: now - HR * 5,
      txHash: '0xabc1230000000000000000000000000000000000000000000000000000000007',
    },
    {
      id: generateId(),
      type: 'bond_formed',
      chain: 'polygon',
      actor: 'sol-writer.vns',
      description: 'sol-writer.vns staked a Bronze accountability bond on Polygon and is now active.',
      timestamp: now - MIN * 45,
      txHash: '0xabc1230000000000000000000000000000000000000000000000000000000008',
      metadata: { bondTier: 'bronze', bondAmount: '0.5 MATIC' },
    },
  ];

  // Store newest-first
  return seeds.reverse();
}
void _legacyActivitySeeds_DO_NOT_CALL;

/* ═══════════════════════════════════════════════════════════════════════════
 * 5. Review System
 * ═══════════════════════════════════════════════════════════════════════════ */

export interface AgentReview {
  id: string;
  taskId: string;
  reviewerAddress: string;
  reviewerName: string;       // VNS name
  revieweeAddress: string;
  revieweeName: string;       // VNS name
  rating: number;             // 1-5
  comment: string;
  contentHash: string;        // SHA-256 of (taskId + reviewerAddress + revieweeAddress + rating + comment)
  chain: 'base' | 'avalanche' | 'arbitrum' | 'polygon';
  timestamp: number;
  verified: boolean;          // true if hash was posted on-chain
  txHash?: string;
}

/** Return all reviews. */
export function getReviews(): AgentReview[] {
  return getStore<AgentReview>(STORAGE.REVIEWS);
}

/** Return all reviews where agentAddress is the reviewee. */
export function getReviewsForAgent(agentAddress: string): AgentReview[] {
  return getReviews().filter(r => r.revieweeAddress === agentAddress);
}

/**
 * Compute a SHA-256 content hash for a review.
 * Uses window.crypto.subtle when available (modern browsers); falls back to a
 * simple djb2-style string hash on environments that lack the Web Crypto API.
 */
export async function computeReviewHash(
  taskId: string,
  reviewerAddress: string,
  revieweeAddress: string,
  rating: number,
  comment: string
): Promise<string> {
  const payload = `${taskId}|${reviewerAddress}|${revieweeAddress}|${rating}|${comment}`;

  // Web Crypto path
  if (typeof window !== 'undefined' && window.crypto?.subtle) {
    const encoder = new TextEncoder();
    const data = encoder.encode(payload);
    const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
  }

  // Fallback: djb2 hash (non-cryptographic, but suitable for a content ID)
  let hash = 5381;
  for (let i = 0; i < payload.length; i++) {
    hash = ((hash << 5) + hash) ^ payload.charCodeAt(i);
    hash = hash >>> 0; // keep as unsigned 32-bit
  }
  return hash.toString(16).padStart(8, '0');
}

/**
 * Create and persist a new agent review.
 * The contentHash is computed asynchronously via computeReviewHash.
 * verified is initialised to false; set it to true after the hash is
 * confirmed on-chain.
 */
export async function createReview(
  params: Omit<AgentReview, 'id' | 'contentHash' | 'timestamp' | 'verified'>
): Promise<AgentReview> {
  const contentHash = await computeReviewHash(
    params.taskId,
    params.reviewerAddress,
    params.revieweeAddress,
    params.rating,
    params.comment
  );

  const review: AgentReview = {
    id: generateId(),
    ...params,
    contentHash,
    timestamp: Date.now(),
    verified: false,
  };

  const reviews = getReviews();
  reviews.unshift(review);
  setStore(STORAGE.REVIEWS, reviews);
  return review;
}
