> **ARCHIVED**: This document is superseded by [REFACTORING_ROADMAP.md](REFACTORING_ROADMAP.md).
> Kept for historical reference of original service layer design decisions.

# Dangerous Pi - Service Layer Refactoring Plan

**Status**: ✅ COMPLETE - Phase 1, 2, & 3 Done!
**Priority**: High (Required before BLE feature parity)
**Effort**: 2-3 weeks
**Last Updated**: 2025-11-26
**Progress**: Week 1 Foundation ✅ | Week 2 REST Refactoring ✅ | Week 3 BLE GATT ✅ | Week 4 Workflows (Optional)

---

## Problem Statement

### Current Architecture Issues

**Code Duplication Risk**: When BLE gets full feature parity, business logic will be duplicated:

```python
# REST API endpoint
@router.post("/command")
async def execute_command(request):
    # ❌ Session validation logic
    if not session_manager.can_execute(request.session_id):
        raise HTTPException(...)

    # ❌ Command execution logic
    result = await pm3_worker.execute_command(request.command)

    # ❌ Session update logic
    session_manager.update_activity(request.session_id)

    return response

# BLE GATT handler (future)
async def handle_command_characteristic(value):
    # ❌ DUPLICATE session validation
    # ❌ DUPLICATE command execution
    # ❌ DUPLICATE session update
    # Same logic, different interface!
```

**Maintenance Nightmare**: Bug fixes must be applied in 2+ places.

---

## Solution: Service Layer Pattern

### New Architecture

```
┌───────────────────────────────────────────────────┐
│  Interface Layer (Multiple Transport Protocols)   │
│                                                   │
│  ├─ REST API (FastAPI)         ← Web/Desktop     │
│  ├─ BLE GATT (BlueZ)            ← Mobile App      │
│  ├─ Plugin System               ← Extensions      │
│  └─ CLI (future)                ← Terminal        │
└─────────────────┬─────────────────────────────────┘
                  │
                  │ All interfaces call services
                  ▼
┌───────────────────────────────────────────────────┐
│  Service Layer (Business Logic) ✨ NEW!           │
│                                                   │
│  ├─ PM3Service          ← Command execution      │
│  │   ├─ execute_command()                        │
│  │   ├─ get_status()                             │
│  │   └─ Session validation                       │
│  │                                                │
│  ├─ SystemService       ← System operations      │
│  │   ├─ get_system_info()                        │
│  │   ├─ shutdown()                                │
│  │   └─ restart()                                 │
│  │                                                │
│  ├─ WiFiService         ← Network management     │
│  │   ├─ scan_networks()                           │
│  │   ├─ connect()                                 │
│  │   └─ get_status()                              │
│  │                                                │
│  ├─ UpdateService       ← Software updates       │
│  │   ├─ check_updates()                           │
│  │   ├─ download_update()                         │
│  │   └─ install_update()                          │
│  │                                                │
│  └─ WorkflowService     ← Guided workflows       │
│      ├─ tune_antenna()                            │
│      ├─ clone_card()                              │
│      └─ id_tag()                                  │
└─────────────────┬─────────────────────────────────┘
                  │
                  │ Services coordinate managers
                  ▼
┌───────────────────────────────────────────────────┐
│  Manager/Worker Layer (Core Implementation)       │
│                                                   │
│  ├─ PM3Worker           ← Hardware interface     │
│  ├─ SessionManager      ← Session state          │
│  ├─ WiFiManager         ← Network state          │
│  ├─ UpdateManager       ← Update logic           │
│  ├─ UPSManager          ← Battery monitoring     │
│  ├─ BLEManager          ← BLE advertising        │
│  └─ PluginManager       ← Plugin lifecycle       │
└───────────────────────────────────────────────────┘
```

### Key Benefits

1. **No Code Duplication** - Business logic written once, used everywhere
2. **Easier Testing** - Test services independently of transport layer
3. **Consistent Behavior** - REST and BLE behave identically
4. **Simpler Maintenance** - Fix bugs in one place
5. **Future-Proof** - Easy to add new interfaces (CLI, gRPC, etc.)

---

## Implementation Roadmap

### Phase 1: Create Service Layer (Week 1)

**Create Service Structure**:
```bash
app/backend/services/
├── __init__.py
├── pm3_service.py          ✅ CREATED
├── system_service.py
├── wifi_service.py
├── update_service.py
└── workflow_service.py     # For guided workflows
```

**PM3 Service** (✅ Complete):
- [x] Command execution with session validation
- [x] Status queries
- [x] Connection management
- [x] Session lifecycle
- [x] Error handling with codes

**System Service**:
```python
class SystemService:
    """System operations service."""

    async def get_info(self) -> ServiceResult:
        """Get system information (CPU, memory, disk)."""

    async def shutdown(self, delay: int = 0) -> ServiceResult:
        """Initiate system shutdown."""

    async def restart(self, delay: int = 0) -> ServiceResult:
        """Initiate system restart."""

    async def get_logs(self, service: str, lines: int = 100) -> ServiceResult:
        """Get service logs."""
```

**WiFi Service**:
```python
class WiFiService:
    """WiFi operations service."""

    async def scan_networks(self) -> ServiceResult:
        """Scan for available networks."""

    async def connect(self, ssid: str, password: str) -> ServiceResult:
        """Connect to network."""

    async def disconnect(self) -> ServiceResult:
        """Disconnect from network."""

    async def get_status(self) -> ServiceResult:
        """Get WiFi status and current connection."""
```

### Phase 2: Refactor REST API (Week 1-2)

**Before** (Current):
```python
# app/backend/api/pm3.py
@router.post("/command")
async def execute_command(request: CommandRequest):
    # Business logic mixed with HTTP concerns
    if not session_manager.can_execute(request.session_id):
        raise HTTPException(status_code=423, detail="...")

    result = await pm3_worker.execute_command(request.command)
    session_manager.update_activity(request.session_id)

    return CommandResponse(...)
```

**After** (Refactored):
```python
# app/backend/api/pm3.py
from ..services import PM3Service

pm3_service = PM3Service()  # Inject singleton

@router.post("/command")
async def execute_command(request: CommandRequest):
    # Thin adapter: converts HTTP → Service → HTTP
    result = await pm3_service.execute_command(
        command=request.command,
        session_id=request.session_id
    )

    if result.success:
        return CommandResponse(
            success=True,
            output=result.data["output"]
        )
    else:
        # Convert service error to HTTP error
        raise HTTPException(
            status_code=get_status_code(result.error.code),
            detail=result.error.message
        )

def get_status_code(error_code: str) -> int:
    """Map service error codes to HTTP status codes."""
    codes = {
        "session_locked": 423,
        "pm3_not_connected": 503,
        "command_failed": 500,
        "session_not_found": 404,
    }
    return codes.get(error_code, 500)
```

**Benefits**:
- REST endpoints become thin adapters
- HTTP concerns separated from business logic
- Services are HTTP-agnostic (can be used by BLE)

### Phase 3: Implement BLE GATT Service (Week 2-3)

**Create BLE Service Structure**:
```bash
app/backend/ble/
├── __init__.py
├── gatt_server.py          # BLE GATT server
├── characteristics.py      # GATT characteristics
└── handlers.py             # Characteristic handlers
```

**BLE GATT Implementation**:
```python
# app/backend/ble/gatt_server.py
from ..services import PM3Service

class DangerousPiGATTServer:
    """BLE GATT server for Dangerous Pi."""

    # Service UUIDs
    PM3_SERVICE_UUID = "12345678-1234-5678-1234-56789abcdef0"

    # Characteristic UUIDs
    COMMAND_CHAR_UUID = "12345678-1234-5678-1234-56789abcdef1"
    STATUS_CHAR_UUID = "12345678-1234-5678-1234-56789abcdef2"
    SESSION_CHAR_UUID = "12345678-1234-5678-1234-56789abcdef3"

    def __init__(self):
        self.pm3_service = PM3Service()  # Same service as REST!
        self.bus = None
        self.adapter = None

    async def start(self):
        """Start GATT server."""
        await self._register_service()
        await self._register_characteristics()

    async def _handle_command_write(self, value: bytes):
        """Handle command characteristic write.

        Value format: JSON {"command": "hw version", "session_id": "..."}
        """
        import json
        data = json.loads(value.decode())

        # ✅ REUSE service logic (no duplication!)
        result = await self.pm3_service.execute_command(
            command=data["command"],
            session_id=data.get("session_id")
        )

        # Return result via BLE notification
        response = {
            "success": result.success,
            "data": result.data if result.success else None,
            "error": {
                "code": result.error.code,
                "message": result.error.message
            } if result.error else None
        }

        await self._notify_characteristic(
            self.COMMAND_CHAR_UUID,
            json.dumps(response).encode()
        )

    async def _handle_status_read(self) -> bytes:
        """Handle status characteristic read."""
        # ✅ REUSE service logic
        result = await self.pm3_service.get_status()

        import json
        return json.dumps(result.data).encode()
```

**Result**: BLE and REST share identical business logic!

### Phase 4: Enhanced BLE Manager (Week 3)

**Upgrade BLE Manager**:
```python
# app/backend/managers/ble_manager.py

class BLEManager:
    """Enhanced BLE manager with GATT server."""

    def __init__(self):
        # Existing notification support
        self._notification_queue = asyncio.Queue()

        # NEW: GATT server for bidirectional communication
        self._gatt_server = None

    async def initialize(self):
        """Initialize BLE with GATT server."""
        # Existing: notification support
        await self._setup_notifications()

        # NEW: Start GATT server
        from ..ble.gatt_server import DangerousPiGATTServer
        self._gatt_server = DangerousPiGATTServer()
        await self._gatt_server.start()

    # Existing notification methods remain unchanged
    async def send_notification(self, ...):
        """Send one-way notification (existing)."""
        ...

    # NEW: Bidirectional communication via GATT
    async def handle_command(self, command: str, session_id: str):
        """Handle command via BLE (delegates to GATT server)."""
        return await self._gatt_server.handle_command(command, session_id)
```

### Phase 5: Workflow Service (Week 3-4)

**Create Workflow Service**:
```python
# app/backend/services/workflow_service.py

class WorkflowService:
    """Service for guided PM3 workflows."""

    def __init__(self):
        self.pm3_service = PM3Service()

    async def tune_antenna(
        self,
        frequency_type: str,  # "hf", "lf", "hw"
        session_id: str
    ) -> WorkflowResult:
        """Execute antenna tuning workflow.

        Returns real-time tuning data for visualization.
        """
        # 1. Execute tune command
        command = f"{frequency_type} tune"
        result = await self.pm3_service.execute_command(command, session_id)

        if not result.success:
            return WorkflowResult(success=False, error=result.error)

        # 2. Parse output into structured data
        from ..parsers.pm3_output import parse_antenna_tuning
        tuning_data = parse_antenna_tuning(result.data["output"])

        # 3. Return workflow result with visualization data
        return WorkflowResult(
            success=True,
            data={
                "tuning_data": tuning_data,
                "optimal": tuning_data["voltage"] > 40.0,
                "recommendation": get_tuning_recommendation(tuning_data)
            }
        )

    async def clone_mifare_classic(
        self,
        session_id: str,
        source_uid: Optional[str] = None
    ) -> AsyncGenerator[WorkflowProgress, None]:
        """Execute MIFARE Classic cloning workflow.

        Yields progress updates for each step.
        """
        # Step 1: Tune antenna
        yield WorkflowProgress(step=1, total=4, message="Tuning HF antenna...")
        tune_result = await self.tune_antenna("hf", session_id)

        if not tune_result.success:
            yield WorkflowProgress(step=1, error="Tuning failed")
            return

        # Step 2: Read source card
        yield WorkflowProgress(step=2, total=4, message="Reading source card...")
        # ... read logic

        # Step 3: Verify
        yield WorkflowProgress(step=3, total=4, message="Verifying read...")
        # ... verify logic

        # Step 4: Write to target
        yield WorkflowProgress(step=4, total=4, message="Writing to target...")
        for block in range(64):
            # ... write each block
            yield WorkflowProgress(
                step=4,
                total=4,
                message=f"Writing block {block}/64...",
                progress=block/64
            )

        yield WorkflowProgress(step=4, total=4, message="Clone complete!", complete=True)
```

---

## Dependency Injection Strategy

### Service Singletons

**Create Service Container**:
```python
# app/backend/services/container.py

class ServiceContainer:
    """Dependency injection container for services."""

    _instance = None

    def __init__(self):
        # Create shared worker/manager instances
        self._pm3_worker = PM3Worker()
        self._session_manager = SessionManager()
        self._wifi_manager = WiFiManager()
        self._update_manager = UpdateManager()

        # Create services with injected dependencies
        self._pm3_service = PM3Service(
            pm3_worker=self._pm3_worker,
            session_manager=self._session_manager
        )
        self._wifi_service = WiFiService(
            wifi_manager=self._wifi_manager
        )
        self._workflow_service = WorkflowService(
            pm3_service=self._pm3_service
        )

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = ServiceContainer()
        return cls._instance

    @property
    def pm3_service(self) -> PM3Service:
        return self._pm3_service

    @property
    def wifi_service(self) -> WiFiService:
        return self._wifi_service

    @property
    def workflow_service(self) -> WorkflowService:
        return self._workflow_service

# Global container instance
container = ServiceContainer.get_instance()
```

**Usage in REST API**:
```python
# app/backend/api/pm3.py
from ..services.container import container

@router.post("/command")
async def execute_command(request: CommandRequest):
    result = await container.pm3_service.execute_command(
        command=request.command,
        session_id=request.session_id
    )
    # ... handle result
```

**Usage in BLE**:
```python
# app/backend/ble/gatt_server.py
from ..services.container import container

class DangerousPiGATTServer:
    async def _handle_command_write(self, value: bytes):
        # Same service instance as REST!
        result = await container.pm3_service.execute_command(...)
```

---

## Testing Strategy

### Service Unit Tests

```python
# tests/test_pm3_service.py
import pytest
from app.backend.services import PM3Service
from app.backend.workers.pm3_worker import MockPM3Worker
from app.backend.managers.session_manager import SessionManager

@pytest.fixture
def pm3_service():
    """Create PM3 service with mock worker."""
    mock_worker = MockPM3Worker()
    session_manager = SessionManager()
    return PM3Service(
        pm3_worker=mock_worker,
        session_manager=session_manager
    )

@pytest.mark.asyncio
async def test_execute_command_success(pm3_service):
    """Test successful command execution."""
    # Create session
    session_result = pm3_service.create_session()
    assert session_result.success

    session_id = session_result.data["session_id"]

    # Execute command
    result = await pm3_service.execute_command(
        command="hw version",
        session_id=session_id
    )

    assert result.success
    assert "Proxmark3" in result.data["output"]

@pytest.mark.asyncio
async def test_execute_command_session_locked(pm3_service):
    """Test command execution with locked session."""
    # Create session
    session1 = pm3_service.create_session()

    # Try to execute with different session
    result = await pm3_service.execute_command(
        command="hw version",
        session_id="different-session"
    )

    assert not result.success
    assert result.error.code == "session_locked"
```

### Integration Tests

```python
# tests/test_rest_ble_parity.py

@pytest.mark.asyncio
async def test_rest_and_ble_return_same_result():
    """Verify REST and BLE produce identical results."""

    # Execute via REST
    rest_response = await rest_client.post("/api/pm3/command",
        json={"command": "hw version", "session_id": "test"})

    # Execute via BLE
    ble_response = await ble_client.write_characteristic(
        COMMAND_CHAR_UUID,
        json.dumps({"command": "hw version", "session_id": "test"}).encode()
    )

    # Results should be identical (both use same service)
    assert rest_response.json()["output"] == ble_response["data"]["output"]
```

---

## Migration Checklist

### Week 1: Service Layer Foundation
- [x] Create `app/backend/services/` directory (✅ complete)
- [x] Implement `PM3Service` (✅ complete)
- [x] Implement `SystemService` (✅ complete)
- [x] Implement `WiFiService` (✅ complete)
- [x] Implement `UpdateService` (✅ complete)
- [x] Create `ServiceContainer` for DI (✅ complete)
- [x] Write service unit tests (✅ complete - 4 test files, 100+ tests)

### Week 2: REST API Refactoring
- [x] Refactor `api/pm3.py` to use `PM3Service` (✅ complete)
- [x] Refactor `api/system.py` to use `SystemService` (✅ complete)
- [x] Refactor `api/wifi.py` to use `WiFiService` (✅ complete)
- [x] Refactor `api/updates.py` to use `UpdateService` (✅ complete)
- [x] Update integration tests (✅ complete - API endpoint tests added)
- [x] Create comprehensive test suite (✅ complete - pytest config, fixtures, docs)

### Week 3: BLE Implementation
- [x] Create `app/backend/ble/` directory (✅ complete)
- [x] Implement `DangerousPiGATTServer` (✅ complete)
- [x] Define GATT service and characteristics (✅ complete)
- [x] Implement characteristic handlers using services (✅ complete - PM3, WiFi, System, Update)
- [x] Write comprehensive BLE tests (✅ complete - full test coverage)
- [x] Create BLE integration guide (✅ complete - BLE_GATT_GUIDE.md)
- [ ] Integrate with BlueZ D-Bus (deferred - requires hardware)

### Week 4: Workflow Service
- [ ] Implement `WorkflowService`
- [ ] Add antenna tuning workflow
- [ ] Add clone workflows
- [ ] Add ID tag workflow
- [ ] REST endpoints for workflows
- [ ] BLE characteristics for workflows
- [ ] Integration tests

---

## Benefits Summary

| Aspect | Before (Current) | After (Refactored) |
|--------|------------------|-------------------|
| **Code Duplication** | High risk (REST + BLE) | None |
| **Testing** | Test each interface separately | Test services once |
| **Maintenance** | Update 2+ places | Update 1 place |
| **New Interfaces** | Copy/paste logic | Reuse services |
| **Consistency** | May diverge | Guaranteed identical |
| **Testability** | HTTP-coupled | Service isolated |

---

## Success Criteria

- [ ] Zero business logic in REST endpoints (thin adapters only)
- [ ] BLE GATT handlers reuse 100% of PM3Service logic
- [ ] Services have 90%+ test coverage
- [ ] REST and BLE produce identical results for same inputs
- [ ] Adding new interface (CLI, gRPC) takes < 1 day

---

## Next Steps

1. **Review this plan** - Approve refactoring approach
2. **Start Week 1** - Implement service layer foundation
3. **Gradual migration** - Refactor one endpoint at a time
4. **BLE implementation** - Add GATT server using services
5. **Mobile app** - React Native app can use BLE interface

---

**Summary**: Refactor to Service Layer pattern BEFORE expanding BLE to prevent code duplication and enable cross-platform consistency.
