/**
 * ComplianceService — AGNTCY Compliance Report generation and Agent Card export.
 *
 * Builds multi-section compliance reports covering agent identity verification
 * and audit trail integrity, and exports all active agents as AGNTCY-standard
 * agent card JSON. Reports are cached in Redis for 5 minutes to avoid
 * repeated expensive DB queries.
 */

import { Pool } from 'pg';
import type { RedisClientType } from 'redis';
import { AuditVerificationService } from './AuditVerificationService.js';

// ============================================================================
// Report interfaces
// ============================================================================

/** Status value for a compliance check section. */
export type ComplianceStatus = 'pass' | 'fail' | 'warn';

/**
 * A single named compliance check section within a full report.
 */
export interface IComplianceSection {
  /** Human-readable section identifier (e.g. 'agent-identity', 'audit-trail'). */
  name: string;
  /** Aggregate status for this section. */
  status: ComplianceStatus;
  /** Human-readable detail string describing the check result. */
  details: string;
}

/**
 * Full AGNTCY compliance report for a single tenant.
 * Returned by generateReport() and cached in Redis.
 */
export interface IComplianceReport {
  /** ISO 8601 timestamp of when this report was generated. */
  generated_at: string;
  /** The tenant (organization) this report covers. */
  tenant_id: string;
  /** AGNTCY schema version this report conforms to. */
  agntcy_schema_version: string;
  /** Ordered list of named compliance sections. */
  sections: IComplianceSection[];
  /** Rolled-up overall status across all sections. */
  overall_status: ComplianceStatus;
  /** Present and true when the report was served from Redis cache. */
  from_cache?: boolean;
}

// ============================================================================
// Agent card interface
// ============================================================================

/**
 * AGNTCY-standard agent card export for a single agent.
 */
export interface IAgentCard {
  /** DID:WEB identifier for the agent, or the raw agent_id if no DID is set. */
  id: string;
  /** Human-readable name (owner field from the agents table). */
  name: string;
  /** List of capability strings declared by the agent. */
  capabilities: string[];
  /** Canonical HTTPS endpoint for this agent on the SentryAgent.ai platform. */
  endpoint: string;
  /** ISO 8601 creation timestamp. */
  created_at: string;
  /** AGNTCY schema version this card conforms to. */
  agntcy_schema_version: string;
}

// ============================================================================
// Internal DB row shapes
// ============================================================================

/** Row returned when querying active agents for a tenant. */
interface AgentRow {
  agent_id: string;
  owner: string;
  capabilities: string[];
  created_at: Date;
  did: string | null;
}

/** Credential check result — one row per agent. */
interface CredentialCheckRow {
  agent_id: string;
  expires_at: Date | null;
  is_expired: boolean;
  expires_soon: boolean;
}

// ============================================================================
// Constants
// ============================================================================

/** Redis TTL in seconds for cached compliance reports (5 minutes). */
const CACHE_TTL_SECONDS = 300;

/** AGNTCY schema version supported by this implementation. */
const AGNTCY_SCHEMA_VERSION = '1.0';

// ============================================================================
// Service
// ============================================================================

/**
 * Service for generating AGNTCY compliance reports and exporting agent cards.
 *
 * Compliance report sections:
 *  - agent-identity: Verifies all active agents have a valid DID and non-expired credential.
 *  - audit-trail: Verifies the cryptographic integrity of the audit hash chain.
 *
 * Reports are cached in Redis under `compliance:report:<tenantId>` for 5 minutes.
 */
export class ComplianceService {
  /** @param pool - PostgreSQL connection pool. */
  /** @param redis - Connected Redis client for report caching. */
  constructor(
    private readonly pool: Pool,
    private readonly redis: RedisClientType,
  ) {}

  // ──────────────────────────────────────────────────────────────────────────
  // Public API
  // ──────────────────────────────────────────────────────────────────────────

  /**
   * Generates an AGNTCY compliance report for the given tenant.
   *
   * The report is cached in Redis at `compliance:report:<tenantId>` for 5 minutes.
   * When a cached result is returned, `from_cache` is set to `true`.
   *
   * @param tenantId - The organization_id of the tenant.
   * @returns The compliance report (possibly from cache).
   */
  async generateReport(tenantId: string): Promise<IComplianceReport> {
    const cacheKey = `compliance:report:${tenantId}`;

    // Attempt cache read
    const cached = await this.redis.get(cacheKey);
    if (cached !== null) {
      const parsed = JSON.parse(cached) as IComplianceReport;
      parsed.from_cache = true;
      return parsed;
    }

    // Build all sections
    const sections: IComplianceSection[] = await Promise.all([
      this.buildAgentIdentitySection(tenantId),
      this.buildAuditTrailSection(tenantId),
    ]);

    const overall_status = this.rollUpStatus(sections);

    const report: IComplianceReport = {
      generated_at: new Date().toISOString(),
      tenant_id: tenantId,
      agntcy_schema_version: AGNTCY_SCHEMA_VERSION,
      sections,
      overall_status,
    };

    // Cache without from_cache field
    await this.redis.set(cacheKey, JSON.stringify(report), { EX: CACHE_TTL_SECONDS });

    return report;
  }

  /**
   * Exports all active (non-decommissioned) agents for the given tenant as
   * AGNTCY-standard agent cards.
   *
   * @param tenantId - The organization_id of the tenant.
   * @returns Array of agent cards.
   */
  async exportAgentCards(tenantId: string): Promise<IAgentCard[]> {
    const result = await this.pool.query<AgentRow>(
      `SELECT agent_id, owner, capabilities, created_at, did
       FROM agents
       WHERE organization_id = $1
         AND status != 'decommissioned'
       ORDER BY created_at ASC`,
      [tenantId],
    );

    return result.rows.map((row) => this.toAgentCard(row));
  }

  // ──────────────────────────────────────────────────────────────────────────
  // Section builders
  // ──────────────────────────────────────────────────────────────────────────

  /**
   * Builds the `agent-identity` compliance section.
   *
   * Checks each active agent for:
   *  - Valid DID (did field must be non-null)
   *  - Non-expired credential (expires_at > NOW())
   *  - Credential expiring within 7 days triggers 'warn' status
   *
   * Status rules (in priority order):
   *  - `fail`: any agent is missing a DID
   *  - `warn`: any credential expires within 7 days
   *  - `pass`: all checks pass
   *
   * @param tenantId - The organization_id to check.
   * @returns The agent-identity section.
   */
  private async buildAgentIdentitySection(tenantId: string): Promise<IComplianceSection> {
    const agentResult = await this.pool.query<AgentRow>(
      `SELECT agent_id, owner, capabilities, created_at, did
       FROM agents
       WHERE organization_id = $1
         AND status != 'decommissioned'`,
      [tenantId],
    );

    const agents = agentResult.rows;

    if (agents.length === 0) {
      return {
        name: 'agent-identity',
        status: 'pass',
        details: 'No active agents found for this tenant.',
      };
    }

    // Check for missing DIDs
    const missingDid = agents.filter((a) => a.did === null || a.did === '');
    if (missingDid.length > 0) {
      return {
        name: 'agent-identity',
        status: 'fail',
        details: `${missingDid.length} agent(s) are missing a DID identifier: ${missingDid.map((a) => a.agent_id).join(', ')}.`,
      };
    }

    // Check credentials for each agent
    const agentIds = agents.map((a) => a.agent_id);
    const credResult = await this.pool.query<CredentialCheckRow>(
      `SELECT
         c.client_id AS agent_id,
         c.expires_at,
         (c.expires_at IS NOT NULL AND c.expires_at <= NOW()) AS is_expired,
         (c.expires_at IS NOT NULL AND c.expires_at <= NOW() + INTERVAL '7 days' AND c.expires_at > NOW()) AS expires_soon
       FROM credentials c
       WHERE c.client_id = ANY($1::uuid[])
         AND c.status = 'active'`,
      [agentIds],
    );

    const credMap = new Map<string, CredentialCheckRow>();
    for (const row of credResult.rows) {
      // Keep the most-recently-checked row (last active credential per agent)
      credMap.set(row.agent_id, row);
    }

    const expiredAgents: string[] = [];
    const expiringSoonAgents: string[] = [];

    for (const agent of agents) {
      const cred = credMap.get(agent.agent_id);
      if (cred) {
        if (cred.is_expired) {
          expiredAgents.push(agent.agent_id);
        } else if (cred.expires_soon) {
          expiringSoonAgents.push(agent.agent_id);
        }
      }
    }

    if (expiredAgents.length > 0) {
      return {
        name: 'agent-identity',
        status: 'fail',
        details: `${expiredAgents.length} agent(s) have expired credentials: ${expiredAgents.join(', ')}.`,
      };
    }

    if (expiringSoonAgents.length > 0) {
      return {
        name: 'agent-identity',
        status: 'warn',
        details: `${expiringSoonAgents.length} agent(s) have credentials expiring within 7 days: ${expiringSoonAgents.join(', ')}.`,
      };
    }

    return {
      name: 'agent-identity',
      status: 'pass',
      details: `All ${agents.length} active agent(s) have valid DIDs and non-expiring credentials.`,
    };
  }

  /**
   * Builds the `audit-trail` compliance section.
   *
   * Delegates to AuditVerificationService.verifyChain() with no date restrictions,
   * covering the full audit history.
   *
   * @param tenantId - Not used directly; AuditVerificationService checks global chain.
   * @returns The audit-trail section.
   */
  private async buildAuditTrailSection(_tenantId: string): Promise<IComplianceSection> {
    const auditService = new AuditVerificationService(this.pool);
    const result = await auditService.verifyChain();

    if (result.verified) {
      return {
        name: 'audit-trail',
        status: 'pass',
        details: `Audit chain intact. ${result.checkedCount} event(s) verified.`,
      };
    }

    return {
      name: 'audit-trail',
      status: 'fail',
      details: `Audit chain integrity failure detected at event ${result.brokenAtEventId ?? 'unknown'}. ${result.checkedCount} event(s) checked before break.`,
    };
  }

  // ──────────────────────────────────────────────────────────────────────────
  // Helpers
  // ──────────────────────────────────────────────────────────────────────────

  /**
   * Computes the rolled-up overall status from all sections.
   * Priority: fail > warn > pass.
   *
   * @param sections - The compliance sections to roll up.
   * @returns The worst status across all sections.
   */
  private rollUpStatus(sections: IComplianceSection[]): ComplianceStatus {
    if (sections.some((s) => s.status === 'fail')) return 'fail';
    if (sections.some((s) => s.status === 'warn')) return 'warn';
    return 'pass';
  }

  /**
   * Maps a raw DB agent row to an AGNTCY agent card.
   *
   * @param row - The agent row from the database.
   * @returns An AGNTCY-standard IAgentCard.
   */
  private toAgentCard(row: AgentRow): IAgentCard {
    return {
      id: row.did ?? row.agent_id,
      name: row.owner,
      capabilities: row.capabilities,
      endpoint: `https://api.sentryagent.ai/agents/${row.agent_id}`,
      created_at: row.created_at.toISOString(),
      agntcy_schema_version: AGNTCY_SCHEMA_VERSION,
    };
  }
}
