Feature: SEC7 - Audit logging for apply
  As a security-conscious developer
  I want all security-relevant operations recorded in an audit log
  So that I have a compliance trail of plan applies, cancellations, and changes

  # ── Recording events ──────────────────────────────────────────

  Scenario: Record a plan_applied event
    Given a fresh audit service
    When I record a "plan_applied" event with plan_id "plan-001" and project "myproject"
    Then the audit log should contain 1 entry
    And the entry event_type should be "plan_applied"
    And the entry plan_id should be "plan-001"
    And the entry project_name should be "myproject"

  Scenario: Record a plan_cancelled event
    Given a fresh audit service
    When I record a "plan_cancelled" event with plan_id "plan-002" and details reason "user request"
    Then the audit log should contain 1 entry
    And the entry event_type should be "plan_cancelled"
    And the entry details should contain key "reason"

  Scenario: Record a resource_modified event
    Given a fresh audit service
    When I record a "resource_modified" event with details resource_id "res-001" and tool "file_write"
    Then the audit log should contain 1 entry
    And the entry event_type should be "resource_modified"
    And the entry details key "tool" should be "file_write"

  Scenario: Record a correction_applied event
    Given a fresh audit service
    When I record a "correction_applied" event with actor "openai/gpt-4o"
    Then the entry actor_name should be "openai/gpt-4o"

  Scenario: Record a config_changed event
    Given a fresh audit service
    When I record a "config_changed" event with user "hamza"
    Then the entry user_identity should be "hamza"

  Scenario: Record an entity_deleted event
    Given a fresh audit service
    When I record a "entity_deleted" event with details entity_type "resource" and entity_name "old-res"
    Then the entry details key "entity_type" should be "resource"
    And the entry details key "entity_name" should be "old-res"

  Scenario: Record a session_created event
    Given a fresh audit service
    When I record a "session_created" event with session_id "sess-001"
    Then the audit log should contain 1 entry
    And the entry event_type should be "session_created"
    And the entry details key "session_id" should be "sess-001"

  Scenario: Record event with empty details
    Given a fresh audit service
    When I record a "plan_applied" event with no details
    Then the audit log should contain 1 entry
    And the entry details should be empty

  Scenario: Record multiple events
    Given a fresh audit service
    When I record 5 "plan_applied" events
    Then the audit log should contain 5 entries

  # ── Querying / filtering ──────────────────────────────────────

  Scenario: List entries filtered by plan_id
    Given a fresh audit service with mixed events
    When I list entries with plan filter "plan-A"
    Then all returned entries should have plan_id "plan-A"

  Scenario: List entries filtered by project
    Given a fresh audit service with mixed events
    When I list entries with project filter "project-X"
    Then all returned entries should have project_name "project-X"

  Scenario: List entries filtered by event_type
    Given a fresh audit service with mixed events
    When I list entries with type filter "plan_cancelled"
    Then all returned entries should have event_type "plan_cancelled"

  Scenario: List entries filtered by since timestamp
    Given a fresh audit service with old and new events
    When I list entries since "2099-01-01T00:00:00"
    Then no entries should be returned

  Scenario: List entries with limit
    Given a fresh audit service with 20 events
    When I list entries with limit 5
    Then exactly 5 entries should be returned

  Scenario: List entries returns newest first
    Given a fresh audit service
    When I record a "plan_applied" event with plan_id "first" and project "p"
    And I record a "plan_applied" event with plan_id "second" and project "p"
    And I list all entries
    Then the first entry should have plan_id "second"

  # ── Show single entry ─────────────────────────────────────────

  Scenario: Show entry by ID
    Given a fresh audit service
    When I record a "plan_applied" event with plan_id "plan-show" and project "proj"
    And I fetch the entry by its ID
    Then the fetched entry should have event_type "plan_applied"
    And the fetched entry should have plan_id "plan-show"

  Scenario: Show non-existent entry returns None
    Given a fresh audit service
    When I fetch entry with ID 99999
    Then no entry should be returned

  # ── Count ──────────────────────────────────────────────────────

  Scenario: Count returns total entries
    Given a fresh audit service
    When I record 7 "config_changed" events
    Then the count should be 7

  Scenario: Count on empty log returns zero
    Given a fresh audit service
    Then the count should be 0

  # ── Retention / pruning ────────────────────────────────────────

  Scenario: Prune deletes entries older than retention days
    Given a fresh audit service with entries from 60 days ago
    When I prune with retention 30 days
    Then the audit log should be empty

  Scenario: Prune keeps recent entries
    Given a fresh audit service
    When I record a "plan_applied" event with plan_id "recent" and project "p"
    And I prune with retention 30 days
    Then the audit log should contain 1 entry

  Scenario: Prune with zero retention keeps everything
    Given a fresh audit service with entries from 60 days ago
    When I prune with retention 0 days
    Then the audit log should not be empty

  Scenario: Prune returns count of deleted entries
    Given a fresh audit service with 10 entries from 90 days ago
    When I prune with retention 30 days
    Then the prune result should be 10

  # ── Settings ───────────────────────────────────────────────────

  Scenario: Default audit retention is zero (keep indefinitely)
    Given default settings
    Then audit_retention_days should be 0

  Scenario: Audit retention can be configured via env var
    Given settings with audit_retention_days 90
    Then audit_retention_days should be 90

  # ── Data class serialization ───────────────────────────────────

  Scenario: AuditLogEntry as_dict returns all fields
    Given a fresh audit service
    When I record a "plan_applied" event with plan_id "p-1" and project "proj-1"
    And I serialize the entry to dict
    Then the dict should contain keys "id" "event_type" "plan_id" "project_name" "created_at" "details"

  Scenario: AuditLogEntry details contain structured data
    Given a fresh audit service
    When I record a "plan_applied" event with details files_changed 3 and validation_passed true
    And I serialize the entry to dict
    Then the dict details key "files_changed" should be 3

  # ── Edge cases ─────────────────────────────────────────────────

  Scenario: Record event with all nullable fields as None
    Given a fresh audit service
    When I record a "plan_applied" event with all nullable fields as None
    Then the entry plan_id should be None
    And the entry project_name should be None
    And the entry actor_name should be None
    And the entry user_identity should be None

  Scenario: Details with non-serializable values use str fallback
    Given a fresh audit service
    When I record a "plan_applied" event with datetime in details
    Then the audit log should contain 1 entry
    And the entry details should contain key "timestamp"

  Scenario: Recording with invalid event_type raises ValueError
    Given a fresh audit service
    When I record an event with invalid event_type "plan_appleid"
    Then a ValueError should be raised for audit service
    And the audit error message should contain "plan_appleid"

  Scenario: Service requires Settings instance
    When I create an AuditService with invalid settings
    Then a TypeError should be raised for audit service

  # ── Context manager and owned session ──────────────────────────

  Scenario: Service context manager opens and closes
    Given a fresh audit service with owned session
    When I use the service as a context manager to record "plan_applied"
    Then the audit context manager result should have event_type "plan_applied"

  Scenario: Service close is safe to call on non-owned session
    Given a fresh audit service
    When I close the service
    Then no audit error should be raised

  Scenario: Row with invalid JSON details falls back to empty dict
    Given a fresh audit service with a corrupt details row
    When I list all entries
    Then exactly 1 entries should be returned
    And the first listed entry details should be empty

  # ── CLI commands ────────────────────────────────────────────────

  Scenario: CLI list shows entries
    Given a fresh audit service
    When I record a "plan_applied" event with plan_id "cli-1" and project "proj"
    And I invoke the audit CLI list command
    Then the CLI exit code should be 0
    And the CLI output should contain "Audit Log"

  Scenario: CLI list with no entries shows empty message
    Given a fresh audit service
    When I invoke the audit CLI list command
    Then the CLI exit code should be 0
    And the CLI output should contain "No audit entries found"

  Scenario: CLI list with filters
    Given a fresh audit service
    When I record a "plan_applied" event with plan_id "f-1" and project "proj"
    And I invoke the audit CLI list command with plan "f-1" and type "plan_applied" and project "proj" and limit 10
    Then the CLI exit code should be 0
    And the CLI output should contain "Audit Log"

  Scenario: CLI show displays entry details
    Given a fresh audit service
    When I record a "config_changed" event with user "hamza"
    And I invoke the audit CLI show command with the entry ID
    Then the CLI exit code should be 0
    And the CLI output should contain "config_changed"
    And the CLI output should contain "hamza"

  Scenario: CLI show with non-existent ID exits with error
    Given a fresh audit service
    When I invoke the audit CLI show command with ID 99999
    Then the CLI exit code should be 1
    And the CLI output should contain "not found"

  Scenario: CLI count shows total
    Given a fresh audit service
    When I record 3 "plan_applied" events
    And I invoke the audit CLI count command
    Then the CLI exit code should be 0
    And the CLI output should contain "3"

  Scenario: CLI prune with yes flag
    Given a fresh audit service with entries from 60 days ago
    When I invoke the audit CLI prune command with days 30 and yes flag
    Then the CLI exit code should be 0
    And the CLI output should contain "Pruned"

  Scenario: CLI prune without yes prompts and aborts
    Given a fresh audit service with entries from 60 days ago
    When I invoke the audit CLI prune command with days 30 and no confirmation
    Then the CLI exit code should be 1

  Scenario: CLI prune with zero retention does nothing
    Given a fresh audit service
    When I invoke the audit CLI prune command with days 0 and no confirmation
    Then the CLI exit code should be 0
    And the CLI output should contain "keep everything"

  # ── Logging configuration ──────────────────────────────────────

  Scenario: configure_structlog sets up development logging
    When I configure structlog for development with level "DEBUG"
    Then structlog should be configured

  Scenario: configure_structlog sets up production logging
    When I configure structlog for production with level "INFO"
    Then structlog should be configured

  Scenario: configure_structlog rejects invalid log level
    When I configure structlog with invalid level "BOGUS"
    Then a ValueError should be raised for audit service

  Scenario: get_logger returns a bound logger
    When I call get_logger with name "test"
    Then a structlog logger should be returned

  # ── Redaction edge cases ────────────────────────────────────────

  Scenario: set_show_secrets rejects non-bool
    When I call set_show_secrets with a non-bool value
    Then a TypeError should be raised for audit service

  Scenario: is_sensitive_key with empty string returns false
    When I check if empty string is sensitive
    Then the sensitivity result should be false

  Scenario: mask_database_url with empty string returns empty
    When I mask an empty database URL
    Then the audit masked URL should be empty

  Scenario: register_pattern rejects empty pattern
    When I register an empty secret pattern
    Then a ValueError should be raised for audit service

  Scenario: secrets_masking_processor skips when show_secrets is true
    When I run the structlog processor with show_secrets enabled
    Then the event dict should be returned unchanged

  Scenario: secrets_masking_processor redacts nested dict values
    When I run the structlog processor with a nested dict value
    Then the nested dict should be redacted
