/**
 * Comprehensive Audit Logging System for LocalGreenChain
 *
 * Provides complete transparency through immutable audit trails
 * tracking all system actions, data modifications, and user activities.
 */

import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';

// Audit Entry Types
export type AuditAction =
  | 'CREATE' | 'UPDATE' | 'DELETE' | 'READ' | 'QUERY'
  | 'TRANSFER' | 'VERIFY' | 'SIGN' | 'EXPORT'
  | 'LOGIN' | 'LOGOUT' | 'API_CALL' | 'ERROR'
  | 'PLANT_REGISTER' | 'PLANT_CLONE' | 'PLANT_TRANSFER'
  | 'TRANSPORT_EVENT' | 'TRANSPORT_VERIFY'
  | 'DEMAND_SIGNAL' | 'SUPPLY_COMMIT'
  | 'FARM_UPDATE' | 'BATCH_CREATE' | 'BATCH_HARVEST'
  | 'AGENT_TASK' | 'AGENT_ALERT'
  | 'SYSTEM_EVENT' | 'CONFIG_CHANGE';

export type AuditSeverity = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL';

export type AuditCategory =
  | 'PLANT' | 'TRANSPORT' | 'DEMAND' | 'SUPPLY'
  | 'FARM' | 'ENVIRONMENT' | 'USER' | 'SYSTEM'
  | 'AGENT' | 'BLOCKCHAIN' | 'API' | 'SECURITY';

export interface AuditActor {
  id: string;
  type: 'USER' | 'SYSTEM' | 'AGENT' | 'API' | 'ANONYMOUS';
  name?: string;
  ip?: string;
  userAgent?: string;
  sessionId?: string;
}

export interface AuditResource {
  type: string;
  id: string;
  name?: string;
  previousState?: any;
  newState?: any;
}

export interface AuditEntry {
  id: string;
  timestamp: string;
  action: AuditAction;
  category: AuditCategory;
  severity: AuditSeverity;
  actor: AuditActor;
  resource?: AuditResource;
  description: string;
  metadata?: Record<string, any>;
  correlationId?: string;
  parentId?: string;
  hash: string;
  previousHash: string;
}

export interface AuditQuery {
  startDate?: string;
  endDate?: string;
  actions?: AuditAction[];
  categories?: AuditCategory[];
  severities?: AuditSeverity[];
  actorId?: string;
  actorType?: AuditActor['type'];
  resourceType?: string;
  resourceId?: string;
  correlationId?: string;
  searchTerm?: string;
  limit?: number;
  offset?: number;
}

export interface AuditStats {
  totalEntries: number;
  entriesByAction: Record<AuditAction, number>;
  entriesByCategory: Record<AuditCategory, number>;
  entriesBySeverity: Record<AuditSeverity, number>;
  entriesByActorType: Record<AuditActor['type'], number>;
  entriesLast24h: number;
  entriesLast7d: number;
  entriesLast30d: number;
  topActors: Array<{ id: string; count: number }>;
  topResources: Array<{ type: string; count: number }>;
  errorRate24h: number;
  lastEntryAt: string | null;
}

export interface AuditReport {
  generatedAt: string;
  period: { start: string; end: string };
  stats: AuditStats;
  highlights: string[];
  anomalies: AuditEntry[];
  complianceStatus: {
    dataIntegrity: boolean;
    chainValid: boolean;
    noTampering: boolean;
  };
}

class AuditLog {
  private entries: AuditEntry[] = [];
  private dataDir: string;
  private dataFile: string;
  private autoSaveInterval: NodeJS.Timeout | null = null;
  private genesisHash = '0';

  constructor() {
    this.dataDir = path.join(process.cwd(), 'data');
    this.dataFile = path.join(this.dataDir, 'audit-log.json');
    this.load();
    this.startAutoSave();
  }

  private load(): void {
    try {
      if (fs.existsSync(this.dataFile)) {
        const data = JSON.parse(fs.readFileSync(this.dataFile, 'utf-8'));
        this.entries = data.entries || [];
        console.log(`[AuditLog] Loaded ${this.entries.length} audit entries`);
      } else {
        this.entries = [];
        this.logSystemEvent('Audit log initialized', 'INFO');
      }
    } catch (error) {
      console.error('[AuditLog] Error loading audit log:', error);
      this.entries = [];
    }
  }

  private save(): void {
    try {
      if (!fs.existsSync(this.dataDir)) {
        fs.mkdirSync(this.dataDir, { recursive: true });
      }
      fs.writeFileSync(this.dataFile, JSON.stringify({
        entries: this.entries,
        exportedAt: new Date().toISOString(),
        version: '1.0.0'
      }, null, 2));
    } catch (error) {
      console.error('[AuditLog] Error saving audit log:', error);
    }
  }

  private startAutoSave(): void {
    // Auto-save every 30 seconds
    this.autoSaveInterval = setInterval(() => this.save(), 30000);
  }

  private generateId(): string {
    return `audit_${Date.now()}_${crypto.randomBytes(8).toString('hex')}`;
  }

  private calculateHash(entry: Omit<AuditEntry, 'hash'>): string {
    const data = JSON.stringify({
      id: entry.id,
      timestamp: entry.timestamp,
      action: entry.action,
      category: entry.category,
      severity: entry.severity,
      actor: entry.actor,
      resource: entry.resource,
      description: entry.description,
      metadata: entry.metadata,
      correlationId: entry.correlationId,
      parentId: entry.parentId,
      previousHash: entry.previousHash
    });
    return crypto.createHash('sha256').update(data).digest('hex');
  }

  private getPreviousHash(): string {
    if (this.entries.length === 0) {
      return this.genesisHash;
    }
    return this.entries[this.entries.length - 1].hash;
  }

  /**
   * Log an audit entry
   */
  log(
    action: AuditAction,
    category: AuditCategory,
    description: string,
    options: {
      severity?: AuditSeverity;
      actor?: Partial<AuditActor>;
      resource?: AuditResource;
      metadata?: Record<string, any>;
      correlationId?: string;
      parentId?: string;
    } = {}
  ): AuditEntry {
    const entry: Omit<AuditEntry, 'hash'> = {
      id: this.generateId(),
      timestamp: new Date().toISOString(),
      action,
      category,
      severity: options.severity || 'INFO',
      actor: {
        id: options.actor?.id || 'system',
        type: options.actor?.type || 'SYSTEM',
        name: options.actor?.name,
        ip: options.actor?.ip,
        userAgent: options.actor?.userAgent,
        sessionId: options.actor?.sessionId
      },
      resource: options.resource,
      description,
      metadata: options.metadata,
      correlationId: options.correlationId,
      parentId: options.parentId,
      previousHash: this.getPreviousHash()
    };

    const hash = this.calculateHash(entry);
    const fullEntry: AuditEntry = { ...entry, hash };

    this.entries.push(fullEntry);

    // Log critical events immediately
    if (fullEntry.severity === 'CRITICAL' || fullEntry.severity === 'ERROR') {
      console.log(`[AuditLog] ${fullEntry.severity}: ${fullEntry.description}`);
      this.save();
    }

    return fullEntry;
  }

  /**
   * Log a system event
   */
  logSystemEvent(description: string, severity: AuditSeverity = 'INFO', metadata?: Record<string, any>): AuditEntry {
    return this.log('SYSTEM_EVENT', 'SYSTEM', description, { severity, metadata });
  }

  /**
   * Log an API call
   */
  logApiCall(
    endpoint: string,
    method: string,
    actor: Partial<AuditActor>,
    options: {
      statusCode?: number;
      responseTime?: number;
      error?: string;
      requestBody?: any;
      queryParams?: any;
    } = {}
  ): AuditEntry {
    const severity: AuditSeverity = options.error ? 'ERROR' :
      (options.statusCode && options.statusCode >= 400) ? 'WARNING' : 'INFO';

    return this.log('API_CALL', 'API', `${method} ${endpoint}`, {
      severity,
      actor,
      metadata: {
        endpoint,
        method,
        statusCode: options.statusCode,
        responseTime: options.responseTime,
        error: options.error,
        requestBody: options.requestBody,
        queryParams: options.queryParams
      }
    });
  }

  /**
   * Log a plant action
   */
  logPlantAction(
    action: 'PLANT_REGISTER' | 'PLANT_CLONE' | 'PLANT_TRANSFER',
    plantId: string,
    actor: Partial<AuditActor>,
    options: {
      previousState?: any;
      newState?: any;
      metadata?: Record<string, any>;
    } = {}
  ): AuditEntry {
    const descriptions: Record<string, string> = {
      PLANT_REGISTER: `Plant ${plantId} registered`,
      PLANT_CLONE: `Plant ${plantId} cloned`,
      PLANT_TRANSFER: `Plant ${plantId} transferred`
    };

    return this.log(action, 'PLANT', descriptions[action], {
      actor,
      resource: {
        type: 'plant',
        id: plantId,
        previousState: options.previousState,
        newState: options.newState
      },
      metadata: options.metadata
    });
  }

  /**
   * Log a transport event
   */
  logTransportEvent(
    eventType: string,
    plantId: string,
    actor: Partial<AuditActor>,
    options: {
      fromLocation?: { lat: number; lng: number };
      toLocation?: { lat: number; lng: number };
      distance?: number;
      carbonFootprint?: number;
      metadata?: Record<string, any>;
    } = {}
  ): AuditEntry {
    return this.log('TRANSPORT_EVENT', 'TRANSPORT', `Transport event: ${eventType} for plant ${plantId}`, {
      actor,
      resource: {
        type: 'transport',
        id: plantId
      },
      metadata: {
        eventType,
        ...options
      }
    });
  }

  /**
   * Log an agent action
   */
  logAgentAction(
    agentName: string,
    taskType: string,
    success: boolean,
    options: {
      executionTime?: number;
      error?: string;
      metadata?: Record<string, any>;
    } = {}
  ): AuditEntry {
    return this.log('AGENT_TASK', 'AGENT', `Agent ${agentName} executed ${taskType}`, {
      severity: success ? 'INFO' : 'WARNING',
      actor: {
        id: agentName,
        type: 'AGENT',
        name: agentName
      },
      metadata: {
        taskType,
        success,
        executionTime: options.executionTime,
        error: options.error,
        ...options.metadata
      }
    });
  }

  /**
   * Log a blockchain action
   */
  logBlockchainAction(
    action: 'CREATE' | 'VERIFY' | 'UPDATE',
    blockIndex: number,
    blockHash: string,
    options: {
      valid?: boolean;
      error?: string;
      metadata?: Record<string, any>;
    } = {}
  ): AuditEntry {
    const severity: AuditSeverity = options.error ? 'ERROR' :
      options.valid === false ? 'WARNING' : 'INFO';

    return this.log(action, 'BLOCKCHAIN', `Block ${blockIndex} ${action.toLowerCase()}d`, {
      severity,
      resource: {
        type: 'block',
        id: blockIndex.toString(),
        name: blockHash.substring(0, 16)
      },
      metadata: {
        blockHash,
        valid: options.valid,
        error: options.error,
        ...options.metadata
      }
    });
  }

  /**
   * Query audit entries
   */
  query(queryParams: AuditQuery): { entries: AuditEntry[]; total: number } {
    let filtered = [...this.entries];

    if (queryParams.startDate) {
      filtered = filtered.filter(e => e.timestamp >= queryParams.startDate!);
    }
    if (queryParams.endDate) {
      filtered = filtered.filter(e => e.timestamp <= queryParams.endDate!);
    }
    if (queryParams.actions && queryParams.actions.length > 0) {
      filtered = filtered.filter(e => queryParams.actions!.includes(e.action));
    }
    if (queryParams.categories && queryParams.categories.length > 0) {
      filtered = filtered.filter(e => queryParams.categories!.includes(e.category));
    }
    if (queryParams.severities && queryParams.severities.length > 0) {
      filtered = filtered.filter(e => queryParams.severities!.includes(e.severity));
    }
    if (queryParams.actorId) {
      filtered = filtered.filter(e => e.actor.id === queryParams.actorId);
    }
    if (queryParams.actorType) {
      filtered = filtered.filter(e => e.actor.type === queryParams.actorType);
    }
    if (queryParams.resourceType) {
      filtered = filtered.filter(e => e.resource?.type === queryParams.resourceType);
    }
    if (queryParams.resourceId) {
      filtered = filtered.filter(e => e.resource?.id === queryParams.resourceId);
    }
    if (queryParams.correlationId) {
      filtered = filtered.filter(e => e.correlationId === queryParams.correlationId);
    }
    if (queryParams.searchTerm) {
      const term = queryParams.searchTerm.toLowerCase();
      filtered = filtered.filter(e =>
        e.description.toLowerCase().includes(term) ||
        e.actor.name?.toLowerCase().includes(term) ||
        e.resource?.name?.toLowerCase().includes(term)
      );
    }

    const total = filtered.length;

    // Sort by timestamp descending (newest first)
    filtered.sort((a, b) => b.timestamp.localeCompare(a.timestamp));

    // Apply pagination
    const offset = queryParams.offset || 0;
    const limit = queryParams.limit || 100;
    filtered = filtered.slice(offset, offset + limit);

    return { entries: filtered, total };
  }

  /**
   * Get audit statistics
   */
  getStats(): AuditStats {
    const now = new Date();
    const day = 24 * 60 * 60 * 1000;

    const entriesByAction: Record<string, number> = {};
    const entriesByCategory: Record<string, number> = {};
    const entriesBySeverity: Record<string, number> = {};
    const entriesByActorType: Record<string, number> = {};
    const actorCounts: Record<string, number> = {};
    const resourceCounts: Record<string, number> = {};

    let entriesLast24h = 0;
    let entriesLast7d = 0;
    let entriesLast30d = 0;
    let errorsLast24h = 0;

    for (const entry of this.entries) {
      // Count by action
      entriesByAction[entry.action] = (entriesByAction[entry.action] || 0) + 1;

      // Count by category
      entriesByCategory[entry.category] = (entriesByCategory[entry.category] || 0) + 1;

      // Count by severity
      entriesBySeverity[entry.severity] = (entriesBySeverity[entry.severity] || 0) + 1;

      // Count by actor type
      entriesByActorType[entry.actor.type] = (entriesByActorType[entry.actor.type] || 0) + 1;

      // Count by actor
      actorCounts[entry.actor.id] = (actorCounts[entry.actor.id] || 0) + 1;

      // Count by resource type
      if (entry.resource?.type) {
        resourceCounts[entry.resource.type] = (resourceCounts[entry.resource.type] || 0) + 1;
      }

      // Time-based counts
      const entryTime = new Date(entry.timestamp).getTime();
      const age = now.getTime() - entryTime;

      if (age <= day) {
        entriesLast24h++;
        if (entry.severity === 'ERROR' || entry.severity === 'CRITICAL') {
          errorsLast24h++;
        }
      }
      if (age <= 7 * day) {
        entriesLast7d++;
      }
      if (age <= 30 * day) {
        entriesLast30d++;
      }
    }

    const topActors = Object.entries(actorCounts)
      .sort((a, b) => b[1] - a[1])
      .slice(0, 10)
      .map(([id, count]) => ({ id, count }));

    const topResources = Object.entries(resourceCounts)
      .sort((a, b) => b[1] - a[1])
      .slice(0, 10)
      .map(([type, count]) => ({ type, count }));

    return {
      totalEntries: this.entries.length,
      entriesByAction: entriesByAction as Record<AuditAction, number>,
      entriesByCategory: entriesByCategory as Record<AuditCategory, number>,
      entriesBySeverity: entriesBySeverity as Record<AuditSeverity, number>,
      entriesByActorType: entriesByActorType as Record<AuditActor['type'], number>,
      entriesLast24h,
      entriesLast7d,
      entriesLast30d,
      topActors,
      topResources,
      errorRate24h: entriesLast24h > 0 ? errorsLast24h / entriesLast24h : 0,
      lastEntryAt: this.entries.length > 0 ? this.entries[this.entries.length - 1].timestamp : null
    };
  }

  /**
   * Verify the integrity of the audit chain
   */
  verifyIntegrity(): { valid: boolean; errors: string[] } {
    const errors: string[] = [];

    for (let i = 0; i < this.entries.length; i++) {
      const entry = this.entries[i];

      // Verify hash
      const { hash, ...entryWithoutHash } = entry;
      const calculatedHash = this.calculateHash(entryWithoutHash as any);

      if (calculatedHash !== hash) {
        errors.push(`Entry ${entry.id} has invalid hash (index ${i})`);
      }

      // Verify chain linkage
      if (i === 0) {
        if (entry.previousHash !== this.genesisHash) {
          errors.push(`First entry should have genesis hash as previousHash`);
        }
      } else {
        if (entry.previousHash !== this.entries[i - 1].hash) {
          errors.push(`Entry ${entry.id} has broken chain link at index ${i}`);
        }
      }
    }

    return {
      valid: errors.length === 0,
      errors
    };
  }

  /**
   * Generate an audit report
   */
  generateReport(startDate: string, endDate: string): AuditReport {
    const { entries } = this.query({ startDate, endDate, limit: 10000 });
    const stats = this.getStats();
    const integrity = this.verifyIntegrity();

    // Find anomalies (errors, critical events)
    const anomalies = entries.filter(e =>
      e.severity === 'ERROR' || e.severity === 'CRITICAL'
    ).slice(0, 50);

    // Generate highlights
    const highlights: string[] = [];

    if (stats.entriesLast24h > 0) {
      highlights.push(`${stats.entriesLast24h} events logged in the last 24 hours`);
    }
    if (stats.errorRate24h > 0.05) {
      highlights.push(`Warning: Error rate is ${(stats.errorRate24h * 100).toFixed(1)}% in last 24h`);
    }
    if (anomalies.length > 0) {
      highlights.push(`${anomalies.length} anomalies detected in the reporting period`);
    }
    if (!integrity.valid) {
      highlights.push(`ALERT: Audit chain integrity compromised - ${integrity.errors.length} issues found`);
    }

    return {
      generatedAt: new Date().toISOString(),
      period: { start: startDate, end: endDate },
      stats,
      highlights,
      anomalies,
      complianceStatus: {
        dataIntegrity: integrity.valid,
        chainValid: integrity.valid,
        noTampering: integrity.errors.length === 0
      }
    };
  }

  /**
   * Get recent entries
   */
  getRecent(limit: number = 50): AuditEntry[] {
    return this.entries.slice(-limit).reverse();
  }

  /**
   * Get entries by correlation ID (for tracking related actions)
   */
  getByCorrelation(correlationId: string): AuditEntry[] {
    return this.entries.filter(e => e.correlationId === correlationId);
  }

  /**
   * Export audit log to various formats
   */
  export(format: 'json' | 'csv' | 'summary', query?: AuditQuery): string {
    const { entries } = this.query(query || { limit: 10000 });

    switch (format) {
      case 'json':
        return JSON.stringify(entries, null, 2);

      case 'csv':
        const headers = ['Timestamp', 'Action', 'Category', 'Severity', 'Actor', 'Actor Type', 'Resource', 'Description'];
        const rows = entries.map(e => [
          e.timestamp,
          e.action,
          e.category,
          e.severity,
          e.actor.id,
          e.actor.type,
          e.resource?.id || '',
          `"${e.description.replace(/"/g, '""')}"`
        ]);
        return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');

      case 'summary':
        const stats = this.getStats();
        return `
AUDIT LOG SUMMARY
=================
Generated: ${new Date().toISOString()}

Total Entries: ${stats.totalEntries}
Last 24 Hours: ${stats.entriesLast24h}
Last 7 Days: ${stats.entriesLast7d}
Last 30 Days: ${stats.entriesLast30d}

Error Rate (24h): ${(stats.errorRate24h * 100).toFixed(2)}%

Top Actions:
${Object.entries(stats.entriesByAction)
  .sort((a, b) => b[1] - a[1])
  .slice(0, 5)
  .map(([action, count]) => `  - ${action}: ${count}`)
  .join('\n')}

Top Categories:
${Object.entries(stats.entriesByCategory)
  .sort((a, b) => b[1] - a[1])
  .slice(0, 5)
  .map(([cat, count]) => `  - ${cat}: ${count}`)
  .join('\n')}
        `.trim();

      default:
        return JSON.stringify(entries);
    }
  }

  /**
   * Force save (for shutdown)
   */
  forceSave(): void {
    this.save();
  }

  /**
   * Cleanup on shutdown
   */
  shutdown(): void {
    if (this.autoSaveInterval) {
      clearInterval(this.autoSaveInterval);
    }
    this.save();
  }
}

// Singleton instance
let auditLogInstance: AuditLog | null = null;

export function getAuditLog(): AuditLog {
  if (!auditLogInstance) {
    auditLogInstance = new AuditLog();
  }
  return auditLogInstance;
}

export default AuditLog;
