export interface Certificate {
  id: string;
  name: string;
  common_name: string;
  sans: string[];
  status: string;
  environment: string;
  issuer_id: string;
  owner_id: string;
  team_id: string;
  renewal_policy_id: string;
  certificate_profile_id: string;
  serial_number?: string;
  fingerprint_sha256?: string;
  key_algorithm?: string;
  key_size?: number;
  issued_at?: string;
  expires_at: string;
  revoked_at?: string;
  revocation_reason?: string;
  target_ids?: string[];
  tags: Record<string, string>;
  last_renewal_at?: string;
  last_deployment_at?: string;
  created_at: string;
  updated_at: string;
}

export const REVOCATION_REASONS = [
  { value: 'unspecified', label: 'Unspecified' },
  { value: 'keyCompromise', label: 'Key Compromise' },
  { value: 'caCompromise', label: 'CA Compromise' },
  { value: 'affiliationChanged', label: 'Affiliation Changed' },
  { value: 'superseded', label: 'Superseded' },
  { value: 'cessationOfOperation', label: 'Cessation of Operation' },
  { value: 'certificateHold', label: 'Certificate Hold' },
  { value: 'privilegeWithdrawn', label: 'Privilege Withdrawn' },
] as const;

export interface CertificateVersion {
  id: string;
  certificate_id: string;
  serial_number: string;
  fingerprint_sha256: string;
  pem_chain: string;
  csr_pem: string;
  not_before: string;
  not_after: string;
  key_algorithm?: string;
  key_size?: number;
  created_at: string;
}

export interface Agent {
  id: string;
  name: string;
  hostname: string;
  ip_address: string;
  os: string;
  architecture: string;
  status: string;
  version: string;
  last_heartbeat: string;
  last_heartbeat_at: string;
  capabilities: string[];
  tags: Record<string, string>;
  registered_at: string;
  created_at: string;
  updated_at: string;
  // I-004: soft-retirement fields. When retired_at is non-null, the agent is
  // tombstoned — it will never heartbeat again and cascaded targets have been
  // retired alongside it. The retired tab on AgentsPage uses these to show the
  // when/why. The server filters retired rows from the default /api/v1/agents
  // listing; they appear only via GET /api/v1/agents/retired.
  retired_at?: string | null;
  retired_reason?: string | null;
}

// I-004: dependency counts returned by the retire handler in both the 200
// success-with-cascade body and the 409 blocked_by_dependencies body. The
// operator UI uses these to show "this agent has N targets, M certs, K jobs
// depending on it" in the confirm-retire dialog.
export interface AgentDependencyCounts {
  active_targets: number;
  active_certificates: number;
  pending_jobs: number;
}

// I-004: success shape for DELETE /api/v1/agents/{id}. already_retired is
// always false for 200 responses; 204 responses carry no body (the retire was
// idempotent — the agent was already retired). The frontend distinguishes by
// HTTP status, not by this field.
export interface RetireAgentResponse {
  retired_at: string;
  already_retired: boolean;
  cascade: boolean;
  counts: AgentDependencyCounts;
}

// I-004: shape returned with HTTP 409 when a retire is blocked by active
// downstream dependencies. Keep in lockstep with the handler's inline struct
// in internal/api/handler/agents.go (search "blocked_by_dependencies").
export interface BlockedByDependenciesResponse {
  error: 'blocked_by_dependencies';
  message: string;
  counts: AgentDependencyCounts;
}

export interface Job {
  id: string;
  certificate_id: string;
  type: string;
  target_id?: string;
  agent_id?: string;
  status: string;
  attempts: number;
  max_attempts: number;
  last_error?: string;
  scheduled_at: string;
  started_at: string;
  completed_at: string;
  created_at: string;
  verification_status?: string;
  verified_at?: string;
  verification_fingerprint?: string;
  verification_error?: string;
}

/**
 * Notification mirrors internal/domain/notification.go#NotificationEvent.
 *
 * I-005 (Notification Retry + Dead-letter Queue) widens the shape with three
 * audit fields:
 *
 *   - retry_count   — number of delivery attempts already consumed (0..5). The
 *                     5-cap is enforced server-side by NotificationsMaxAttempts.
 *   - next_retry_at — RFC3339 timestamp the retry sweep will next consider this
 *                     notification. Null for sent/dead/read and between sweeps
 *                     for pending rows; the sweep populates it on each failure
 *                     using min(2^retry_count * 1m, 1h).
 *   - last_error    — most recent transient delivery failure. Preserved across
 *                     requeue so Dead letter triage shows *why* the row died
 *                     without chasing server logs.
 *
 * `sent_at` and `error` are the pre-I-005 audit fields on the backend struct.
 * `subject` is a historical frontend-only field the backend never emits; it's
 * kept optional so legacy fixtures and the pendingNotif test mock still type
 * correctly without forcing a rewrite of every existing consumer.
 *
 * Status values follow the backend NotificationStatus constants:
 *   pending · sent · failed · dead · read
 * The existing list view tolerates the legacy title-cased "Pending" alias at
 * render time (NotificationRow) so upgraded clients talking to older servers
 * don't regress — see `isUnread` logic in NotificationsPage.tsx.
 */
export interface Notification {
  id: string;
  type: string;
  channel: string;
  recipient: string;
  subject?: string;
  message: string;
  status: string;
  certificate_id?: string;
  sent_at?: string | null;
  error?: string | null;
  retry_count?: number;
  next_retry_at?: string | null;
  last_error?: string | null;
  created_at: string;
}

export interface AuditEvent {
  id: string;
  actor: string;
  actor_type: string;
  action: string;
  resource_type: string;
  resource_id: string;
  details: Record<string, unknown>;
  timestamp: string;
}

/**
 * Policy rule type enum — pinned to the backend's TitleCase constants in
 * internal/domain/policy.go. Historical note (D-005): the GUI previously sent
 * lowercase values (`ownership`, `environment`, etc.) that the handler's
 * ValidatePolicyType rejected with a 400. These tuples are the canonical
 * source of truth for the dropdown options; the regression test in
 * types.test.ts pins them so future drift is caught at CI time.
 */
export const POLICY_TYPES = [
  'AllowedIssuers',
  'AllowedDomains',
  'RequiredMetadata',
  'AllowedEnvironments',
  'RenewalLeadTime',
  'CertificateLifetime',
] as const;
export type PolicyType = (typeof POLICY_TYPES)[number];

/**
 * Policy severity enum — pinned to the backend's PolicySeverity constants.
 * The backend CHECK constraint on policy_rules.severity enforces the same
 * allowlist (migration 000013). The 4-value `medium` option that used to
 * appear in the GUI was never a valid backend value and has been removed.
 */
export const POLICY_SEVERITIES = ['Warning', 'Error', 'Critical'] as const;
export type PolicySeverity = (typeof POLICY_SEVERITIES)[number];

export interface PolicyRule {
  id: string;
  name: string;
  type: PolicyType;
  severity: PolicySeverity;
  config: Record<string, unknown>;
  enabled: boolean;
  created_at: string;
  updated_at: string;
}

export interface PolicyViolation {
  id: string;
  rule_id: string;
  certificate_id: string;
  severity: PolicySeverity;
  message: string;
  created_at: string;
}

/**
 * G-1: RenewalPolicy is the lifecycle policy attached to managed certificates
 * via `managed_certificates.renewal_policy_id` (FK ON DELETE RESTRICT → `rp-*`
 * IDs in the `renewal_policies` table). Distinct from `PolicyRule` above, which
 * models compliance rules in the `policy_rules` table with `pol-*` IDs. The
 * OnboardingWizard + CertificatesPage + CertificateDetailPage dropdowns populate
 * `renewal_policy_id` from this interface — previously they mis-populated it
 * from `getPolicies()` which returned `pol-*` IDs and produced FK violations on
 * certificate insert/update.
 *
 * JSON tags mirror internal/domain/renewal_policy.go.
 */
export interface RenewalPolicy {
  id: string;
  name: string;
  renewal_window_days: number;
  auto_renew: boolean;
  max_retries: number;
  retry_interval_seconds: number;
  alert_thresholds_days: number[];
  certificate_profile_id?: string | null;
  created_at: string;
  updated_at: string;
}

export interface Issuer {
  id: string;
  name: string;
  type: string;
  config: Record<string, unknown>;
  status: string;
  /** Backend returns enabled boolean; status is derived from this */
  enabled: boolean;
  /** Timestamp of last connection test */
  last_tested_at?: string;
  /** Result of last connection test: "untested", "success", or "failed" */
  test_status?: string;
  /** Config source: "database" (GUI-created) or "env" (env var seeded) */
  source?: string;
  created_at: string;
  updated_at?: string;
}

export interface Target {
  id: string;
  name: string;
  type: string;
  agent_id: string;
  config: Record<string, unknown>;
  enabled: boolean;
  last_tested_at?: string;
  test_status?: string;
  source?: string;
  created_at: string;
  updated_at?: string;
}

export interface KeyAlgorithmRule {
  algorithm: string;
  min_size: number;
}

export interface CertificateProfile {
  id: string;
  name: string;
  description: string;
  allowed_key_algorithms: KeyAlgorithmRule[];
  max_ttl_seconds: number;
  allowed_ekus: string[];
  required_san_patterns: string[];
  spiffe_uri_pattern: string;
  allow_short_lived: boolean;
  enabled: boolean;
  created_at: string;
  updated_at: string;
}

export interface Owner {
  id: string;
  name: string;
  email: string;
  team_id: string;
  created_at: string;
  updated_at: string;
}

export interface Team {
  id: string;
  name: string;
  description: string;
  created_at: string;
  updated_at: string;
}

export interface AgentGroup {
  id: string;
  name: string;
  description: string;
  match_os: string;
  match_architecture: string;
  match_ip_cidr: string;
  match_version: string;
  enabled: boolean;
  created_at: string;
  updated_at: string;
}

export interface AgentGroupMembership {
  agent_group_id: string;
  agent_id: string;
  membership_type: string;
  created_at: string;
}

export interface PaginatedResponse<T> {
  data: T[];
  total: number;
  page: number;
  per_page: number;
}

// Stats types
export interface DashboardSummary {
  total_certificates: number;
  expiring_certificates: number;
  expired_certificates: number;
  revoked_certificates: number;
  active_agents: number;
  offline_agents: number;
  total_agents: number;
  pending_jobs: number;
  failed_jobs: number;
  complete_jobs: number;
  completed_at: string;
}

export interface CertificateStatusCount {
  status: string;
  count: number;
}

export interface ExpirationBucket {
  date: string;
  count: number;
}

export interface JobTrendDataPoint {
  date: string;
  completed_count: number;
  failed_count: number;
  success_rate: number;
}

export interface IssuanceRateDataPoint {
  date: string;
  issued_count: number;
}

// Discovery types
export interface DiscoveredCertificate {
  id: string;
  fingerprint_sha256: string;
  common_name: string;
  sans: string[];
  serial_number: string;
  issuer_dn: string;
  subject_dn: string;
  not_before?: string;
  not_after?: string;
  key_algorithm: string;
  key_size: number;
  is_ca: boolean;
  source_path: string;
  source_format: string;
  agent_id: string;
  discovery_scan_id?: string;
  managed_certificate_id?: string;
  status: string;
  first_seen_at: string;
  last_seen_at: string;
  dismissed_at?: string;
  created_at: string;
  updated_at: string;
}

export interface DiscoveryScan {
  id: string;
  agent_id: string;
  directories: string[];
  certificates_found: number;
  certificates_new: number;
  errors_count: number;
  scan_duration_ms: number;
  started_at: string;
  completed_at?: string;
}

export interface DiscoverySummary {
  Unmanaged: number;
  Managed: number;
  Dismissed: number;
}

// Network scan types
export interface NetworkScanTarget {
  id: string;
  name: string;
  cidrs: string[];
  ports: number[];
  enabled: boolean;
  scan_interval_hours: number;
  timeout_ms: number;
  last_scan_at?: string;
  last_scan_duration_ms?: number;
  last_scan_certs_found?: number;
  created_at: string;
  updated_at: string;
}

export interface MetricsResponse {
  gauge: {
    certificate_total: number;
    certificate_active: number;
    certificate_expiring_soon: number;
    certificate_expired: number;
    certificate_revoked: number;
    agent_total: number;
    agent_online: number;
    job_pending: number;
  };
  counter: {
    job_completed_total: number;
    job_failed_total: number;
  };
  uptime: {
    uptime_seconds: number;
    server_started: string;
    measured_at: string;
  };
}

// Health check types (M48)
export interface EndpointHealthCheck {
  id: string;
  endpoint: string;
  certificate_id?: string;
  network_scan_target_id?: string;
  expected_fingerprint: string;
  observed_fingerprint: string;
  status: string;
  consecutive_failures: number;
  response_time_ms: number;
  tls_version: string;
  cipher_suite: string;
  cert_subject: string;
  cert_issuer: string;
  cert_expiry?: string;
  last_checked_at?: string;
  last_success_at?: string;
  last_failure_at?: string;
  last_transition_at?: string;
  failure_reason: string;
  degraded_threshold: number;
  down_threshold: number;
  check_interval_seconds: number;
  enabled: boolean;
  acknowledged: boolean;
  acknowledged_by?: string;
  acknowledged_at?: string;
  created_at: string;
  updated_at: string;
}

export interface HealthHistoryEntry {
  id: string;
  health_check_id: string;
  status: string;
  response_time_ms: number;
  fingerprint: string;
  failure_reason: string;
  checked_at: string;
}

export interface HealthCheckSummary {
  healthy: number;
  degraded: number;
  down: number;
  cert_mismatch: number;
  unknown: number;
  total: number;
}
