# Phase 2 Implementation Plan: Mitra Online Status & Pairing Logic

## Summary of Clarified Requirements

| Topic | Decision |
|---|---|
| Mitra default status | Offline on app open; must explicitly toggle online |
| Auto-offline | Yes — on app close / connection loss |
| Online/offline logs | Backend + control center only; mitra don't see their own logs |
| Multi-device | No — single device per mitra |
| Customer active requests | One at a time; one active session at a time |
| Search timeout | 60 seconds, then "no bestie available" message |
| Cancel while searching | Yes |
| Pairing blast | Valkey pub/sub (in-app real-time); push notifications deferred |
| Race condition | First-come-first-served |
| Mitra decline/ignore | Allowed; request stays open until 60s timeout or customer cancel |
| Session end | Explicit end only (time-based deferred to next phase) |
| Payment placeholder | `pending_payment` status, skip to `active` for now |
| Max customer per mitra | Global config (not per-mitra) |
| Reroute | Forced assignment (acceptance-based deferred) |
| Reroute target | Online mitras only |
| Dashboard refresh | Polling-based auto-refresh now; SSE/push later |

---

## 1. Database Changes

### 1.1 New table: `mitra_online_status`

Tracks the current online/offline state per mitra.

| Column | Type | Notes |
|---|---|---|
| `id` | `SERIAL PRIMARY KEY` | |
| `mitra_id` | `INT REFERENCES mitras(id)` | Unique — one row per mitra |
| `is_online` | `BOOLEAN DEFAULT false` | |
| `last_online_at` | `TIMESTAMPTZ` | Last time they went online |
| `last_offline_at` | `TIMESTAMPTZ` | Last time they went offline |
| `updated_at` | `TIMESTAMPTZ` | |

### 1.2 New table: `mitra_online_logs`

Append-only log for control center reporting.

| Column | Type | Notes |
|---|---|---|
| `id` | `SERIAL PRIMARY KEY` | |
| `mitra_id` | `INT REFERENCES mitras(id)` | |
| `status` | `VARCHAR` | `'online'` or `'offline'` |
| `timestamp` | `TIMESTAMPTZ DEFAULT now()` | |

### 1.3 New table: `chat_sessions`

Tracks pairing and session lifecycle.

| Column | Type | Notes |
|---|---|---|
| `id` | `SERIAL PRIMARY KEY` | |
| `customer_id` | `INT REFERENCES customers(id)` | |
| `mitra_id` | `INT REFERENCES mitras(id)` | Nullable — null while searching |
| `status` | `VARCHAR` | See statuses below |
| `created_at` | `TIMESTAMPTZ DEFAULT now()` | |
| `paired_at` | `TIMESTAMPTZ` | When mitra accepted |
| `ended_at` | `TIMESTAMPTZ` | When session explicitly ended |
| `ended_by` | `VARCHAR` | `'customer'`, `'mitra'`, or `'system'` |

**Session statuses:**
- `searching` — customer requested, waiting for mitra
- `pending_acceptance` — blast sent, waiting for a mitra to accept
- `pending_payment` — mitra accepted, payment placeholder (auto-skip for now)
- `active` — session in progress
- `completed` — ended explicitly
- `cancelled` — customer cancelled during search
- `expired` — 60s timeout, no mitra accepted
- `rerouted` — session reassigned by control center (old record status)

### 1.4 New table: `chat_request_notifications`

Tracks which mitras were notified of a pairing request (for blast tracking).

| Column | Type | Notes |
|---|---|---|
| `id` | `SERIAL PRIMARY KEY` | |
| `session_id` | `INT REFERENCES chat_sessions(id)` | |
| `mitra_id` | `INT REFERENCES mitras(id)` | |
| `notified_at` | `TIMESTAMPTZ DEFAULT now()` | |
| `response` | `VARCHAR` | `'accepted'`, `'declined'`, `'ignored'` |
| `responded_at` | `TIMESTAMPTZ` | |

### 1.5 Extend `app_config`

Add new config key:

| Key | Value (JSONB) | Purpose |
|---|---|---|
| `max_customers_per_mitra` | `{ "value": 3 }` | Global max concurrent sessions per mitra |

---

## 2. Backend Changes

### 2.1 Valkey (Redis-compatible) Setup

- Add Valkey client as Fastify plugin (`src/plugins/valkey.js`)
- Used for pub/sub channels:
  - `mitra:{mitra_id}:requests` — notify specific mitra of incoming chat requests
  - `session:{session_id}:status` — notify customer of session status changes
- This pub/sub infrastructure will be reused for real-time chat in the next phase

### 2.2 New Public Routes — Mitra

| Method | Path | Purpose |
|---|---|---|
| `POST` | `/api/mitra/status/online` | Set mitra online |
| `POST` | `/api/mitra/status/offline` | Set mitra offline |
| `GET` | `/api/mitra/status` | Get own current status |
| `GET` | `/api/mitra/chat-requests/incoming` | SSE/long-poll endpoint for incoming chat request notifications |
| `POST` | `/api/mitra/chat-requests/:sessionId/accept` | Accept a chat request |
| `POST` | `/api/mitra/chat-requests/:sessionId/decline` | Decline a chat request |
| `GET` | `/api/mitra/sessions/active` | Get mitra's active sessions |
| `POST` | `/api/mitra/sessions/:sessionId/end` | End a session |

### 2.3 New Public Routes — Client

| Method | Path | Purpose |
|---|---|---|
| `POST` | `/api/client/chat/request` | Start a pairing request |
| `GET` | `/api/client/chat/request/:sessionId/status` | SSE/long-poll for pairing status updates |
| `POST` | `/api/client/chat/request/:sessionId/cancel` | Cancel a pairing request |
| `GET` | `/api/client/sessions/active` | Get customer's active session |
| `POST` | `/api/client/sessions/:sessionId/end` | End a session |

### 2.4 New Internal Routes — Control Center

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/internal/dashboard/stats` | Active chats, active mitras, active requests, customers per mitra |
| `GET` | `/internal/config/max-customers-per-mitra` | Get current max config |
| `PATCH` | `/internal/config/max-customers-per-mitra` | Update max config |
| `GET` | `/internal/sessions` | List sessions (filterable by status) |
| `GET` | `/internal/sessions/:sessionId` | Session detail |
| `POST` | `/internal/sessions/:sessionId/reroute` | Reroute session to another mitra (forced) |
| `GET` | `/internal/mitras/online` | List online mitras with active session count |
| `GET` | `/internal/mitras/:mitraId/online-logs` | Online/offline log for a mitra |

### 2.5 New Services

| Service | Responsibilities |
|---|---|
| `mitra-status.service.js` | Toggle online/offline, log entries, auto-offline logic |
| `pairing.service.js` | Create pairing request, blast to mitras, handle accept/decline, timeout (60s), cancel |
| `session.service.js` | Session lifecycle (active, end, reroute), active session queries |
| `dashboard.service.js` | Aggregate stats for control center dashboard |

### 2.6 Pairing Flow (Backend Detail)

```
1. Customer calls POST /api/client/chat/request
2. Backend creates chat_session (status: searching)
3. Backend queries available mitras:
   - is_online = true
   - active_session_count < max_customers_per_mitra config
4. If no mitra available → immediately return "no bestie available"
5. If mitras available:
   - Update session status → pending_acceptance
   - Create chat_request_notifications for each available mitra
   - Publish to each mitra's Valkey channel
   - Start 60s timeout timer (via setTimeout or scheduled job)
6. Mitra calls POST /api/mitra/chat-requests/:sessionId/accept
   - Check session still in pending_acceptance (first-come-first-served)
   - If yes → pair, set session status → pending_payment → active (skip payment)
   - Publish status update to customer's session channel
   - Mark other mitra notifications as "ignored"
7. On 60s timeout with no accept:
   - Set session status → expired
   - Publish timeout to customer's session channel
8. On customer cancel:
   - Set session status → cancelled
   - Notify mitras to dismiss the request
```

### 2.7 Auto-Offline Mechanism

- Mitra app sends periodic heartbeat (`POST /api/mitra/status/heartbeat`) every 15 seconds
- Backend tracks last heartbeat timestamp in `mitra_online_status`
- A background interval (every 30s) checks for mitras with `is_online = true` but no heartbeat in the last 45 seconds → auto-set offline
- This handles force-close, network loss, and crashes

---

## 3. Mitra App Changes

### 3.1 New BLoC: `StatusBloc`

Manages online/offline toggle and heartbeat.

**Events:** `ToggleOnline`, `ToggleOffline`, `HeartbeatTick`, `AutoOfflineDetected`
**States:** `StatusInitial`, `StatusOnline`, `StatusOffline`, `StatusLoading`, `StatusError`

- On `ToggleOnline` → call API, start heartbeat timer (15s interval)
- On `ToggleOffline` → call API, stop heartbeat timer
- On app lifecycle `paused`/`detached` → call offline API, stop heartbeat

### 3.2 New BLoC: `ChatRequestBloc`

Handles incoming chat request notifications.

**Events:** `StartListening`, `StopListening`, `RequestReceived`, `AcceptRequest`, `DeclineRequest`
**States:** `Idle`, `IncomingRequest(session)`, `Accepting`, `Accepted(session)`, `Declined`, `Error`

- Listens to SSE/long-poll endpoint for incoming requests while mitra is online
- Shows incoming request UI overlay/bottom sheet

### 3.3 Screen Changes

| Screen | Changes |
|---|---|
| Home screen | Add online/offline toggle switch at the top; show active session count |
| New: Incoming request sheet | Bottom sheet showing customer request with Accept/Decline buttons |
| New: Active sessions screen | List of current active sessions (name, duration) with End button |

### 3.4 App Lifecycle Handling

- Use `WidgetsBindingObserver` to detect app going to background/foreground
- On `paused`/`detached` → call offline API
- On `resumed` → do NOT auto-set online; mitra must explicitly toggle

---

## 4. Client App Changes

### 4.1 New BLoC: `PairingBloc`

Manages the pairing request lifecycle.

**Events:** `RequestPairing`, `CancelPairing`, `PairingStatusUpdate`, `PairingTimeout`
**States:** `PairingInitial`, `Searching`, `BestieFound(mitraName)`, `Paired(session)`, `NoBestieAvailable`, `Cancelled`, `Error`

- On `RequestPairing` → call API, start listening for status updates, start 60s local timer
- On status update `active` → emit `BestieFound` briefly, then `Paired`
- On timeout / expired → emit `NoBestieAvailable`

### 4.2 Screen Changes

| Screen | Changes |
|---|---|
| Home screen | Add "Mulai Curhat" CTA button (disabled if already has active session) |
| New: Searching screen | "Searching for Bestie..." with animation and Cancel button |
| New: Bestie found screen | Brief "Bestie ditemukan, menghubungkan kamu ke Bestie" message |
| New: Session active screen | Shows paired bestie name, End Session button |
| New: No bestie screen | "No bestie available, try again later" with retry button |

### 4.3 Navigation Updates

Add new routes in GoRouter:
- `/chat/searching` — searching screen
- `/chat/session/:sessionId` — active session screen

---

## 5. Control Center Changes

### 5.1 New Pages

| Page | Purpose |
|---|---|
| Dashboard page | Real-time stats: active chats, online mitras, pending requests, customers per mitra breakdown |
| Session management page | Table of all sessions (filterable by status), reroute action |
| Session detail page | Session info, customer/mitra details, reroute button |

### 5.2 Updated Pages

| Page | Changes |
|---|---|
| Settings page | Add "Max customers per mitra" config input |
| Mitra management page | Add online status indicator column, online/offline log link |

### 5.3 Dashboard Auto-Refresh

- Use React Query with `refetchInterval: 10000` (10s polling)
- Later can be swapped to SSE push without changing component logic

---

## 6. Implementation Order

Work should be done in this sequence to allow incremental testing:

| Step | What | Apps affected |
|---|---|---|
| 1 | Database migration (new tables + config) | Backend |
| 2 | Valkey plugin setup | Backend |
| 3 | Mitra online/offline status API + service + heartbeat | Backend |
| 4 | Mitra app: status toggle + heartbeat + lifecycle handling | Mitra app |
| 5 | Control center: max config + mitra online status column + logs | Control center |
| 6 | Pairing service + chat request APIs (mitra + client) | Backend |
| 7 | Client app: pairing flow (request, searching, found, paired screens) | Client app |
| 8 | Mitra app: incoming request notification + accept/decline | Mitra app |
| 9 | Session management APIs (end, reroute, list) | Backend |
| 10 | Client/mitra app: active session screen + end session | Both apps |
| 11 | Control center: dashboard + session management + reroute | Control center |

---

## 7. New Dependencies

| App | Package | Purpose |
|---|---|---|
| Backend | `ioredis` or `@valkey/valkey-glide` | Valkey/Redis client for pub/sub |
| Mitra app | `dio` (existing) | SSE via streaming response |
| Client app | `dio` (existing) | SSE via streaming response |

No major new dependencies needed — leveraging existing stack.

---

## 8. Note for Next Phase

**Valkey vs FCM — complementary, not competing:**
- **Valkey pub/sub** is the real-time transport for in-app events (pairing blasts, status updates, and later chat messages). Works when the app is in foreground.
- **FCM (Firebase Cloud Messaging)** should be added in the next phase for push notifications when the app is backgrounded/closed (e.g. "New message from customer"). Not needed this phase because mitras auto-go offline when the app is backgrounded — they only receive pairing requests while in foreground.
- Architecture: Valkey for real-time transport, FCM for push notifications. They layer on top of each other.
