# ADR-003: State Machine Pattern

## Status

Accepted

## Context

Parks and rides in ThrillWiki go through various operational states:
- Parks: Operating, Closed Temporarily, Closed Permanently, Under Construction
- Rides: Operating, Closed, Under Construction, Removed, Relocated

Managing these state transitions requires:
- Valid state transition enforcement
- Audit trail of state changes
- Business logic tied to state changes (notifications, cache invalidation)

## Decision

We implemented a **Finite State Machine (FSM) pattern** for managing entity states, using django-fsm with custom enhancements.

### State Model

```python
from django_fsm import FSMField, transition

class Park(models.Model):
    status = FSMField(default='OPERATING')

    @transition(
        field=status,
        source=['OPERATING', 'CLOSED_TEMP'],
        target='CLOSED_PERM'
    )
    def close_permanently(self, reason=None):
        """Close the park permanently."""
        self.closure_reason = reason
        self.closure_date = timezone.now()

    @transition(
        field=status,
        source='CLOSED_TEMP',
        target='OPERATING'
    )
    def reopen(self):
        """Reopen a temporarily closed park."""
        self.closure_reason = None
```

### State Diagram

```
Park States:
                    ┌──────────────┐
                    │   PLANNED    │
                    └──────┬───────┘
                           │
                           ▼
    ┌──────────────────────────────────────┐
    │           UNDER_CONSTRUCTION          │
    └──────────────────┬───────────────────┘
                       │
                       ▼
    ┌──────────────────────────────────────┐
    │              OPERATING               │◄────┐
    └──────────────────┬───────────────────┘     │
                       │                         │
           ┌───────────┼───────────┐             │
           │           │           │             │
           ▼           ▼           ▼             │
    ┌────────────┐ ┌────────┐ ┌────────────┐     │
    │CLOSED_TEMP │ │SEASONAL│ │CLOSED_PERM │     │
    └─────┬──────┘ └────────┘ └────────────┘     │
          │                                       │
          └───────────────────────────────────────┘
```

### Transition Validation

```python
class ParkStateTransition(models.Model):
    """Audit log for park state transitions."""
    park = models.ForeignKey(Park, on_delete=models.CASCADE)
    from_state = models.CharField(max_length=20)
    to_state = models.CharField(max_length=20)
    transition_date = models.DateTimeField(auto_now_add=True)
    transitioned_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    reason = models.TextField(blank=True)
```

## Consequences

### Benefits

1. **Valid Transitions Only**: Invalid state changes are rejected at the model level
2. **Audit Trail**: All transitions are logged with timestamps and users
3. **Business Logic Encapsulation**: Transition methods contain related logic
4. **Testability**: State machines are easy to unit test
5. **Documentation**: State diagrams document valid workflows

### Trade-offs

1. **Learning Curve**: Developers need to understand FSM concepts
2. **Migration Complexity**: Adding new states requires careful migration
3. **Flexibility**: Rigid state transitions can be limiting for edge cases

### State Change Hooks

```python
from django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save, sender=Park)
def park_state_change(sender, instance, **kwargs):
    if instance.pk:
        old_instance = Park.objects.get(pk=instance.pk)
        if old_instance.status != instance.status:
            # Log transition
            ParkStateTransition.objects.create(
                park=instance,
                from_state=old_instance.status,
                to_state=instance.status,
            )
            # Invalidate caches
            invalidate_park_caches(instance)
            # Send notifications
            notify_state_change(instance, old_instance.status)
```

## Alternatives Considered

### Simple Status Field

**Rejected because:**
- No validation of state transitions
- Business logic scattered across codebase
- No built-in audit trail

### Event Sourcing

**Rejected because:**
- Overkill for current requirements
- Significant complexity increase
- Steeper learning curve

### Workflow Engine

**Rejected because:**
- External dependency overhead
- More complex than needed
- FSM sufficient for current use cases

## Implementation Details

### Ride Status States

```python
class RideStatus(models.TextChoices):
    OPERATING = 'OPERATING', 'Operating'
    CLOSED_TEMP = 'CLOSED_TEMP', 'Temporarily Closed'
    CLOSED_PERM = 'CLOSED_PERM', 'Permanently Closed'
    UNDER_CONSTRUCTION = 'UNDER_CONSTRUCTION', 'Under Construction'
    REMOVED = 'REMOVED', 'Removed'
    RELOCATED = 'RELOCATED', 'Relocated'
```

### Testing State Transitions

```python
class ParkStateTransitionTest(TestCase):
    def test_cannot_reopen_permanently_closed_park(self):
        park = ParkFactory(status='CLOSED_PERM')
        with self.assertRaises(TransitionNotAllowed):
            park.reopen()

    def test_can_close_operating_park_temporarily(self):
        park = ParkFactory(status='OPERATING')
        park.close_temporarily(reason='Maintenance')
        self.assertEqual(park.status, 'CLOSED_TEMP')
```

## References

- [django-fsm Documentation](https://github.com/viewflow/django-fsm)
- [State Machine Diagrams](../state_machines/diagrams.md)
- [Finite State Machine Wikipedia](https://en.wikipedia.org/wiki/Finite-state_machine)
