Feature: Concurrency Locks
  As a developer
  I want plan-level and project-level advisory locks
  So that concurrent modifications to shared resources are prevented

  Background:
    Given I have a lock service backed by an in-memory database

  # --- Acquisition ---

  Scenario: Acquire a plan lock
    When I acquire a lock on plan "plan-001" as owner "session-a"
    Then the lock should be acquired successfully

  Scenario: Acquire a project lock
    When I acquire a lock on project "local/my-proj" as owner "session-a"
    Then the lock should be acquired successfully

  # --- Re-entrant acquisition ---

  Scenario: Same owner re-acquires a plan lock
    Given owner "session-a" holds a lock on plan "plan-001"
    When I acquire a lock on plan "plan-001" as owner "session-a"
    Then the lock should be acquired successfully

  # --- Conflict detection ---

  Scenario: Different owner is rejected with LockConflictError
    Given owner "session-a" holds a lock on plan "plan-001"
    When I try to acquire a lock on plan "plan-001" as owner "session-b"
    Then a lock conflict error should be raised for owner "session-a"

  # --- Release ---

  Scenario: Release a held lock
    Given owner "session-a" holds a lock on plan "plan-001"
    When I release the lock on plan "plan-001" as owner "session-a"
    Then the lock should be released successfully

  Scenario: Release returns false for non-existent lock
    When I release the lock on plan "plan-999" as owner "nobody"
    Then the release result should be false

  # --- Renewal ---

  Scenario: Renew a held lock
    Given owner "session-a" holds a lock on plan "plan-001"
    When I renew the lock on plan "plan-001" as owner "session-a"
    Then the lock renewal should succeed

  Scenario: Renew an expired lock raises LockExpiredError
    Given owner "session-a" held an expired lock on plan "plan-001"
    When I try to renew the expired lock on plan "plan-001" as owner "session-a"
    Then a lock expired error should be raised

  # --- Expiry and cleanup ---

  Scenario: Expired lock is replaced by a new owner
    Given owner "session-a" held an expired lock on plan "plan-001"
    When I acquire a lock on plan "plan-001" as owner "session-b"
    Then the lock should be acquired successfully

  Scenario: Cleanup expired locks on startup
    Given there are 3 expired locks in the database
    When I run cleanup expired locks
    Then the expired lock count should be 3

  # --- Graceful shutdown ---

  Scenario: Release all locks for an owner on shutdown
    Given owner "session-a" holds locks on plans "p1" and "p2" and project "proj-1"
    When I release all locks for owner "session-a"
    Then the released count should be 3

  # --- Diagnostics ---

  Scenario: Count stale locks for diagnostics
    Given there are 2 expired locks in the database
    When I count stale locks
    Then the stale lock count should be 2

  # --- is_locked check ---

  Scenario: is_locked returns true for active lock
    Given owner "session-a" holds a lock on plan "plan-001"
    When I check if plan "plan-001" is locked
    Then the resource should be locked

  Scenario: is_locked returns false when no lock exists
    When I check if plan "plan-999" is locked
    Then the resource should not be locked

  # --- Validation ---

  Scenario: Reject empty owner_id
    When I try to acquire a lock with empty owner_id
    Then a lock validation error should be raised

  Scenario: Reject invalid resource type
    When I try to acquire a lock with invalid resource type "widget"
    Then a lock validation error should be raised

  Scenario: Reject empty resource_id
    When I try to acquire a lock with empty resource_id
    Then a lock validation error should be raised

  Scenario: Reject TTL below minimum
    When I try to acquire a lock with TTL 2
    Then a lock validation error should be raised

  Scenario: Reject TTL above maximum
    When I try to acquire a lock with TTL 7200
    Then a lock validation error should be raised

  # --- Additional validation ---

  Scenario: Reject None session_factory
    When I try to create a lock service with None session factory
    Then a lock validation error should be raised

  Scenario: Reject empty resource_type on acquire
    When I try to acquire a lock with empty resource type
    Then a lock validation error should be raised

  Scenario: Reject empty owner_id on release
    When I try to release a lock with empty owner_id
    Then a lock validation error should be raised

  Scenario: Reject empty owner_id on renew
    When I try to renew a lock with empty owner_id
    Then a lock validation error should be raised

  Scenario: Reject empty owner_id on release_all_for_owner
    When I try to release all locks with empty owner_id
    Then a lock validation error should be raised

  Scenario: Renew returns false for nonexistent lock
    When I try to renew a lock that does not exist
    Then the renew result should be false
