<?php
/**
 * Migration: create `affilun_audit_events` table (schema version 7).
 *
 * Installs the audit-trail table described in
 * `specifications/database-schema.md` §10. The table holds the
 * canonical evidence stream for configuration changes, destructive
 * operations, security-relevant events, and compliance-adjacent
 * actions — for example provider settings updates, credential
 * rotations, sync-policy changes, product deletions, archive /
 * unarchive transitions, forced re-syncs, cache clears, diagnostics
 * resets, migration runs, and compliance-relevant configuration
 * changes (§10.1).
 *
 * Per §10.4 this table is deliberately distinct from the operational
 * log (`affilun_logs`, v6): audit events are retained longer (180–365+
 * days vs. the level-scaled operational retention), may be gated
 * behind stricter viewer permissions, and are designed to be reusable
 * as evidence for release gates, aftercare reviews, and incident
 * post-mortems. The same real-world occurrence MAY appear in both
 * `affilun_logs` and `affilun_audit_events`, but the retention target,
 * access policy, and storage intent differ.
 *
 * This migration targets {@see SchemaVersion::of(7)} — the seventh and
 * final initial-release step in affilun's schema
 * pipeline, installed after `affilun_products` (v1), `affilun_product_meta`
 * (v2), `affilun_embeds` (v3), `affilun_embed_items` (v4), `affilun_sync_jobs`
 * (v5), and `affilun_logs` (v6).
 *
 * Scope of this migration:
 *
 *   - Creates `{$prefix}affilun_audit_events` with the full column set
 *     defined in §10.2 (thirteen columns: surrogate primary key,
 *     event-type and audit-kind discriminators, actor and target
 *     descriptors, action verb, result code, human-readable safe
 *     summary, structured changed-fields and context JSON blobs, and
 *     a creation timestamp — audit rows are append-only so there is
 *     no `updated_at`).
 *
 *   - Adds the six secondary indexes listed in §10.3 (`idx_event_type`,
 *     `idx_audit_kind`, `idx_actor`, `idx_target`, `idx_created_at`,
 *     `idx_event_type_created`) that support the common audit query
 *     patterns: drill down by event type or audit kind, filter by
 *     actor or target, window by time, and combine event-type with
 *     time for release-gate evidence gathering.
 *
 *   - Uses the charset/collation returned by
 *     `$wpdb->get_charset_collate()` (per §17.2) and relies on
 *     `InnoDB` as the storage engine (§17.1).
 *
 * Scope explicitly NOT covered here:
 *
 *   - `CHECK (audit_kind IN (...))` / `CHECK (result IN (...))`
 *     constraints. §10.2 enumerates the initial-version values
 *     (`configuration_change`, `destructive_action`, `security_event`,
 *     `operational_action`, `compliance_relevant_action` for
 *     `audit_kind`; `success`, `failed`, `rejected` for `result`), but
 *     `dbDelta()` does not reliably round-trip `CHECK` constraints
 *     across MySQL / MariaDB versions (per §13.4). Application-layer
 *     validation on the audit-write path is the canonical enforcement
 *     point.
 *
 *   - JSON-extraction function indexes on `context_json`
 *     (`idx_request_id`, `idx_correlation_id`). §10.3 lists these as
 *     "必要に応じて" (optional) and explicitly permits substituting
 *     alternatives depending on database dialect and operational
 *     constraints, so they are not part of the initial schema. Future
 *     migrations may add them on environments where the `JSON_EXTRACT`
 *     functional index form is supported (MySQL 8.0+ / MariaDB 10.5+).
 *
 *   - Foreign keys. Per §11.2 and §13.4, AFFILUN does not declare foreign
 *     keys at the DB level. The `actor_*` / `target_*` pair is kept as
 *     free-form string discriminators rather than typed references so
 *     audit rows survive deletion of their original subject (a deleted
 *     product or user MUST still appear in the audit trail that
 *     records its deletion).
 *
 *   - `DEFAULT CURRENT_TIMESTAMP` on `created_at`. Per §13.4,
 *     environment-sensitive default clauses are avoided; the timestamp
 *     is supplied by the application layer on insert.
 *
 *   - Audit retention / purge logic (§10.4). This migration only
 *     creates the table that makes long retention possible; the
 *     scheduled purge and the "purge-of-audit is itself auditable"
 *     rule belong in the runtime cleanup path and the audit-write
 *     contract, respectively.
 *
 *   - Any DML. This migration creates the empty table only; seed data
 *     and backfills belong in later migrations or runtime code paths.
 *
 * The shared DDL-assembly and installer-dispatch skeleton lives in
 * {@see AbstractCreateTableMigration}; this subclass only declares the
 * bits that actually differ between tables (target version,
 * description, logical name, column manifest, index manifest). See the
 * base class's file-level docblock for the idempotency and
 * failure-propagation guarantees inherited here.
 *
 * @package Affilun\Infrastructure\Persistence\Migrations
 */

declare(strict_types=1);

namespace Affilun\Infrastructure\Persistence\Migrations;

use Affilun\Infrastructure\Persistence\SchemaVersion;

/**
 * Creates the `affilun_audit_events` table at schema version 7.
 *
 * Concrete {@see AbstractCreateTableMigration} subclass. See the
 * file-level docblock for the scope of this migration and the columns
 * and indexes it installs; see the base class for the formatting rules
 * and failure semantics that apply to every AFFILUN table-creation
 * migration.
 */
final class CreateAuditEventsTable extends AbstractCreateTableMigration {

	/**
	 * Logical, unprefixed AFFILUN table name installed by this migration.
	 *
	 * Kept as a class-level constant so tests can reference the exact
	 * name without duplicating the string literal, and so a future
	 * rename has a single location to update.
	 */
	public const TABLE_LOGICAL_NAME = 'affilun_audit_events';

	/**
	 * Target schema version installed by this migration.
	 *
	 * Returns version 7 — the revision that adds the audit-trail
	 * table on top of `affilun_products` (v1), `affilun_product_meta` (v2),
	 * `affilun_embeds` (v3), `affilun_embed_items` (v4), `affilun_sync_jobs` (v5),
	 * and `affilun_logs` (v6). See `database-schema.md` §13.2 for the
	 * canonical version numbering.
	 */
	public function targetVersion(): SchemaVersion {
		return SchemaVersion::of( 7 );
	}

	/**
	 * Short, human-readable description of this migration.
	 *
	 * Used by the diagnostics surface and migration logs. Kept terse
	 * and operator-friendly per the
	 * {@see \Affilun\Infrastructure\Persistence\Migration::description()}
	 * contract.
	 */
	public function description(): string {
		return 'create affilun_audit_events table';
	}

	/**
	 * Logical, unprefixed AFFILUN table name installed by this migration.
	 *
	 * Consumed by {@see AbstractCreateTableMigration::up()} and
	 * {@see AbstractCreateTableMigration::tableName()} to resolve the
	 * physical, prefix-applied name through the injected
	 * {@see \Affilun\Infrastructure\Persistence\TableNameResolver}.
	 */
	protected function logicalTableName(): string {
		return self::TABLE_LOGICAL_NAME;
	}

	/**
	 * Column definitions for `affilun_audit_events`, in the canonical
	 * order defined by `database-schema.md` §10.2.
	 *
	 * The table is append-only: there is no `updated_at` column
	 * because audit rows are never mutated in place. Corrections to a
	 * previously-recorded event are themselves recorded as new audit
	 * rows so the evidence trail remains tamper-evident.
	 *
	 * `event_type` and `action` are sized `varchar(64)` per §10.2 to
	 * accommodate dotted identifiers such as `settings.updated` with
	 * comfortable headroom. `actor_type` / `target_type` and
	 * `audit_kind` / `result` are sized `varchar(32)` to fit the
	 * enumerated initial-version values (`configuration_change`,
	 * `compliance_relevant_action`, etc.) with headroom for future
	 * additions. `actor_id` / `target_id` are sized `varchar(64)` so
	 * numeric IDs, UUIDs, and short stable string identifiers all fit
	 * without per-subject-type special-casing.
	 *
	 * `safe_summary` is `text` (not `longtext`) because §10.2 requires
	 * a human-readable summary rather than full payload capture; the
	 * full structured diff lives in `changed_fields_json`, and
	 * correlation metadata lives in `context_json`. Keeping the
	 * summary shorter also discourages accidentally embedding secrets
	 * in free-form text.
	 *
	 * Each entry is a single column definition without its trailing
	 * comma; the base class adds the indent and inter-column commas.
	 *
	 * @return list<string>
	 */
	protected function columnDefinitions(): array {
		return array(
			'id bigint unsigned NOT NULL AUTO_INCREMENT',
			'event_type varchar(64) NOT NULL',
			'audit_kind varchar(32) NOT NULL',
			'actor_type varchar(32) NULL',
			'actor_id varchar(64) NULL',
			'target_type varchar(32) NULL',
			'target_id varchar(64) NULL',
			'action varchar(64) NOT NULL',
			'result varchar(32) NOT NULL',
			'safe_summary text NOT NULL',
			'changed_fields_json longtext NULL',
			'context_json longtext NULL',
			'created_at datetime NOT NULL',
		);
	}

	/**
	 * Index definitions for `affilun_audit_events`, in the canonical
	 * order defined by `database-schema.md` §10.3.
	 *
	 * The `PRIMARY KEY` entry uses the two-space separator that
	 * `dbDelta()` relies on to detect the primary key line — this is
	 * a WordPress-specific formatting quirk, not a typo.
	 *
	 * The composite `idx_actor` and `idx_target` indexes support the
	 * common "show me every audit event for this user" / "show me
	 * every audit event on this product" drill-down from the admin
	 * diagnostics surface. The composite `idx_event_type_created`
	 * index accelerates release-gate evidence queries of the form
	 * "every `settings.updated` event in the last N days" without
	 * forcing a full table scan of the long-retention audit table.
	 *
	 * Optional JSON-extraction function indexes on `context_json`
	 * (`idx_request_id`, `idx_correlation_id`) listed in §10.3 are
	 * NOT installed here; see the file-level docblock for the
	 * rationale.
	 *
	 * @return list<string>
	 */
	protected function indexDefinitions(): array {
		return array(
			'PRIMARY KEY  (id)',
			'KEY idx_event_type (event_type)',
			'KEY idx_audit_kind (audit_kind)',
			'KEY idx_actor (actor_type, actor_id)',
			'KEY idx_target (target_type, target_id)',
			'KEY idx_created_at (created_at)',
			'KEY idx_event_type_created (event_type, created_at)',
		);
	}
}
