Skip to main content

legalis_api/
lib.rs

1//! Legalis-API: REST/GraphQL API server for Legalis-RS.
2//!
3//! This crate provides a web API for interacting with the Legalis-RS framework:
4//! - CRUD operations for statutes
5//! - Verification endpoints
6//! - Simulation endpoints
7//! - Registry queries
8//! - OpenAPI 3.0 documentation
9//! - Authentication and authorization (RBAC + ReBAC)
10
11pub mod ai_suggestions;
12pub mod anomaly;
13pub mod async_jobs;
14pub mod audit;
15pub mod auth;
16pub mod cache;
17pub mod collaborative;
18pub mod config;
19pub mod contract_test;
20// pub mod dataloader; // TODO: Re-enable when Loader trait signature issues are resolved
21pub mod edge_cache;
22pub mod field_selection;
23pub mod gateway;
24pub mod graphql;
25#[cfg(feature = "grpc")]
26pub mod grpc;
27pub mod live_queries;
28pub mod load_test;
29pub mod logging;
30mod metrics;
31pub mod multitenancy;
32pub mod oauth2_provider;
33pub mod observability;
34pub mod openapi;
35pub mod persisted_queries;
36pub mod presence;
37pub mod query_batch;
38pub mod query_cost;
39pub mod rate_limit;
40pub mod rebac;
41pub mod sampling;
42pub mod schema_stitching;
43pub mod security;
44pub mod slo;
45pub mod telemetry;
46pub mod versioning;
47pub mod websocket;
48
49// Event-Driven Architecture (v0.2.4)
50pub mod cqrs;
51pub mod event_replay;
52pub mod event_schema;
53pub mod event_sourcing;
54pub mod event_streaming;
55
56// Developer Experience (v0.2.5)
57pub mod changelog;
58pub mod mocking;
59pub mod playground;
60pub mod sdk_generator;
61pub mod sdk_notifications;
62pub mod test_utils;
63
64use axum::{
65    Extension, Json, Router,
66    extract::{Path, Query, State},
67    http::StatusCode,
68    middleware,
69    response::{
70        IntoResponse,
71        sse::{Event, KeepAlive, Sse},
72    },
73    routing::{get, post},
74};
75use futures::stream::{self, Stream};
76use legalis_core::Statute;
77use legalis_viz::DecisionTree;
78use serde::{Deserialize, Serialize};
79use std::convert::Infallible;
80use std::sync::Arc;
81use std::time::Duration;
82use thiserror::Error;
83use tokio::sync::RwLock;
84use tower_http::{compression::CompressionLayer, cors::CorsLayer};
85use tracing::info;
86
87/// API errors.
88#[derive(Debug, Error)]
89pub enum ApiError {
90    #[error("Not found: {0}")]
91    NotFound(String),
92
93    #[error("Invalid request: {0}")]
94    BadRequest(String),
95
96    #[error("Internal error: {0}")]
97    Internal(String),
98
99    #[error("Validation failed: {0}")]
100    ValidationFailed(String),
101
102    #[error(transparent)]
103    Auth(#[from] auth::AuthError),
104}
105
106impl IntoResponse for ApiError {
107    fn into_response(self) -> axum::response::Response {
108        let (status, message) = match self {
109            ApiError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
110            ApiError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
111            ApiError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
112            ApiError::ValidationFailed(msg) => (StatusCode::UNPROCESSABLE_ENTITY, msg),
113            ApiError::Auth(err) => return err.into_response(),
114        };
115
116        let body = Json(ErrorResponse { error: message });
117        (status, body).into_response()
118    }
119}
120
121/// Error response body.
122#[derive(Serialize)]
123struct ErrorResponse {
124    error: String,
125}
126
127/// Success response wrapper.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct ApiResponse<T> {
130    pub data: T,
131    pub meta: Option<ResponseMeta>,
132}
133
134impl<T> ApiResponse<T> {
135    pub fn new(data: T) -> Self {
136        Self { data, meta: None }
137    }
138
139    pub fn with_meta(mut self, meta: ResponseMeta) -> Self {
140        self.meta = Some(meta);
141        self
142    }
143}
144
145/// Response metadata.
146#[derive(Debug, Clone, Serialize, Deserialize, Default)]
147pub struct ResponseMeta {
148    pub total: Option<usize>,
149    pub page: Option<usize>,
150    pub per_page: Option<usize>,
151    pub next_cursor: Option<String>,
152    pub prev_cursor: Option<String>,
153    pub has_more: Option<bool>,
154}
155
156/// Verification job result.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct VerificationJobResult {
159    pub passed: bool,
160    pub errors: Vec<String>,
161    pub warnings: Vec<String>,
162    pub statute_count: usize,
163}
164
165/// Saved simulation result.
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct SavedSimulation {
168    pub id: String,
169    pub name: String,
170    pub description: Option<String>,
171    pub statute_ids: Vec<String>,
172    pub population_size: usize,
173    pub deterministic_outcomes: usize,
174    pub discretionary_outcomes: usize,
175    pub void_outcomes: usize,
176    pub deterministic_rate: f64,
177    pub discretionary_rate: f64,
178    pub void_rate: f64,
179    pub created_at: String,
180    pub created_by: String,
181}
182
183/// Application state.
184pub struct AppState {
185    /// In-memory statute storage
186    pub statutes: RwLock<Vec<Statute>>,
187    /// ReBAC authorization engine
188    pub rebac: RwLock<rebac::ReBACEngine>,
189    /// Async verification job manager
190    pub verification_jobs: async_jobs::JobManager<VerificationJobResult>,
191    /// Saved simulations
192    pub saved_simulations: RwLock<Vec<SavedSimulation>>,
193    /// Response cache
194    pub cache: Arc<cache::CacheStore>,
195    /// WebSocket broadcaster for real-time notifications
196    pub ws_broadcaster: websocket::WsBroadcaster,
197    /// Audit log for tracking all mutations
198    pub audit_log: Arc<audit::AuditLog>,
199    /// API keys storage
200    pub api_keys: RwLock<Vec<auth::ApiKey>>,
201    /// Collaborative editor for real-time editing
202    pub collaborative_editor: Arc<collaborative::CollaborativeEditor>,
203    /// Presence manager for tracking active users
204    pub presence_manager: Arc<presence::PresenceManager>,
205}
206
207impl AppState {
208    pub fn new() -> Self {
209        Self {
210            statutes: RwLock::new(Vec::new()),
211            rebac: RwLock::new(rebac::ReBACEngine::new()),
212            verification_jobs: async_jobs::JobManager::new(),
213            saved_simulations: RwLock::new(Vec::new()),
214            cache: Arc::new(cache::CacheStore::new()),
215            ws_broadcaster: websocket::WsBroadcaster::new(),
216            audit_log: Arc::new(audit::AuditLog::new()),
217            api_keys: RwLock::new(Vec::new()),
218            collaborative_editor: Arc::new(collaborative::CollaborativeEditor::new()),
219            presence_manager: Arc::new(presence::PresenceManager::new(30)),
220        }
221    }
222}
223
224impl Default for AppState {
225    fn default() -> Self {
226        Self::new()
227    }
228}
229
230/// Statute list response.
231#[derive(Serialize)]
232pub struct StatuteListResponse {
233    pub statutes: Vec<StatuteSummary>,
234}
235
236/// Statute summary for list views.
237#[derive(Serialize)]
238pub struct StatuteSummary {
239    pub id: String,
240    pub title: String,
241    pub has_discretion: bool,
242    pub precondition_count: usize,
243}
244
245/// Statute permission update request.
246#[derive(Deserialize)]
247pub struct StatutePermissionRequest {
248    /// User ID to grant/revoke permission
249    pub user_id: String,
250    /// Permission type (viewer, editor, owner)
251    pub permission: String,
252}
253
254/// Statute permission list response.
255#[derive(Serialize)]
256pub struct StatutePermissionsResponse {
257    pub statute_id: String,
258    pub permissions: Vec<StatutePermissionEntry>,
259}
260
261/// Statute permission entry.
262#[derive(Serialize)]
263pub struct StatutePermissionEntry {
264    pub user_id: String,
265    pub permission: String,
266}
267
268impl From<&Statute> for StatuteSummary {
269    fn from(s: &Statute) -> Self {
270        Self {
271            id: s.id.clone(),
272            title: s.title.clone(),
273            has_discretion: s.discretion_logic.is_some(),
274            precondition_count: s.preconditions.len(),
275        }
276    }
277}
278
279/// Create statute request.
280#[derive(Deserialize)]
281pub struct CreateStatuteRequest {
282    pub statute: Statute,
283}
284
285/// Verification request.
286#[derive(Deserialize)]
287pub struct VerifyRequest {
288    pub statute_ids: Vec<String>,
289}
290
291/// Verification response.
292#[derive(Serialize)]
293pub struct VerifyResponse {
294    pub passed: bool,
295    pub errors: Vec<String>,
296    pub warnings: Vec<String>,
297}
298
299/// Async verification start response.
300#[derive(Serialize)]
301pub struct AsyncVerifyStartResponse {
302    pub job_id: String,
303    pub status: String,
304    pub poll_url: String,
305}
306
307/// Job status response.
308#[derive(Serialize)]
309pub struct JobStatusResponse<T> {
310    pub id: String,
311    pub status: String,
312    pub progress: f32,
313    pub result: Option<T>,
314    pub error: Option<String>,
315    pub created_at: String,
316    pub updated_at: String,
317}
318
319/// Detailed verification report response.
320#[derive(Serialize)]
321pub struct DetailedVerifyResponse {
322    pub passed: bool,
323    pub total_errors: usize,
324    pub total_warnings: usize,
325    pub total_suggestions: usize,
326    pub errors: Vec<String>,
327    pub warnings: Vec<String>,
328    pub suggestions: Vec<String>,
329    pub statute_count: usize,
330    pub verified_at: String,
331}
332
333/// Batch verification request - verifies multiple groups of statutes independently.
334#[derive(Deserialize)]
335pub struct BatchVerifyRequest {
336    /// Each entry is a separate verification job with its own statute IDs
337    pub jobs: Vec<VerifyJob>,
338}
339
340/// A single verification job within a batch.
341#[derive(Deserialize)]
342pub struct VerifyJob {
343    /// Optional job ID for tracking
344    pub job_id: Option<String>,
345    /// Statute IDs to verify in this job
346    pub statute_ids: Vec<String>,
347}
348
349/// Batch verification response.
350#[derive(Serialize)]
351pub struct BatchVerifyResponse {
352    /// Results for each verification job
353    pub results: Vec<BatchVerifyResult>,
354    /// Total jobs processed
355    pub total_jobs: usize,
356    /// Number of jobs that passed
357    pub passed_jobs: usize,
358    /// Number of jobs that failed
359    pub failed_jobs: usize,
360}
361
362/// Result for a single verification job in a batch.
363#[derive(Serialize)]
364pub struct BatchVerifyResult {
365    /// Job ID (if provided)
366    pub job_id: Option<String>,
367    /// Verification result
368    pub passed: bool,
369    /// Errors found
370    pub errors: Vec<String>,
371    /// Warnings found
372    pub warnings: Vec<String>,
373    /// Number of statutes verified
374    pub statute_count: usize,
375}
376
377/// Complexity analysis response.
378#[derive(Serialize)]
379pub struct ComplexityResponse {
380    pub statute_id: String,
381    pub complexity_score: f64,
382    pub precondition_count: usize,
383    pub nesting_depth: usize,
384    pub has_discretion: bool,
385}
386
387/// Search/filter parameters for statutes.
388#[derive(Deserialize)]
389pub struct StatuteSearchQuery {
390    /// Search by title (case-insensitive substring match)
391    pub title: Option<String>,
392    /// Filter by whether statute has discretion
393    pub has_discretion: Option<bool>,
394    /// Filter by minimum number of preconditions
395    pub min_preconditions: Option<usize>,
396    /// Filter by maximum number of preconditions
397    pub max_preconditions: Option<usize>,
398    /// Limit number of results
399    pub limit: Option<usize>,
400    /// Offset for pagination
401    pub offset: Option<usize>,
402    /// Cursor for cursor-based pagination
403    pub cursor: Option<String>,
404    /// Field selection (comma-separated list of fields)
405    pub fields: Option<String>,
406}
407
408/// Statute comparison request.
409#[derive(Deserialize)]
410pub struct StatuteComparisonRequest {
411    pub statute_id_a: String,
412    pub statute_id_b: String,
413}
414
415/// Statute comparison matrix request.
416#[derive(Deserialize)]
417pub struct StatuteComparisonMatrixRequest {
418    /// List of statute IDs to compare
419    pub statute_ids: Vec<String>,
420}
421
422/// Statute comparison matrix response.
423#[derive(Serialize)]
424pub struct StatuteComparisonMatrixResponse {
425    /// Statutes being compared
426    pub statutes: Vec<StatuteSummary>,
427    /// Matrix of similarity scores (indexed by statute order)
428    pub similarity_matrix: Vec<Vec<f64>>,
429    /// Detailed comparison pairs
430    pub comparisons: Vec<ComparisonMatrixEntry>,
431}
432
433/// Entry in the comparison matrix.
434#[derive(Serialize)]
435pub struct ComparisonMatrixEntry {
436    pub statute_a_id: String,
437    pub statute_b_id: String,
438    pub similarity_score: f64,
439    pub precondition_diff: i32,
440    pub discretion_differs: bool,
441}
442
443/// API key creation request.
444#[derive(Deserialize)]
445pub struct CreateApiKeyRequest {
446    /// Name/description for the key
447    pub name: String,
448    /// Role for the key
449    pub role: auth::Role,
450    /// Optional scoped permissions (if not provided, uses all role permissions)
451    pub scopes: Option<Vec<String>>,
452    /// Optional expiration in days (if not provided, never expires)
453    pub expires_in_days: Option<i64>,
454}
455
456/// API key response (with the actual key value shown only once).
457#[derive(Serialize)]
458pub struct ApiKeyResponse {
459    pub id: String,
460    pub key: Option<String>, // Only shown on creation
461    pub name: String,
462    pub role: String,
463    pub scopes: Vec<String>,
464    pub created_at: String,
465    pub expires_at: Option<String>,
466    pub active: bool,
467    pub last_used_at: Option<String>,
468}
469
470/// API key list response.
471#[derive(Serialize)]
472pub struct ApiKeyListResponse {
473    pub keys: Vec<ApiKeyResponse>,
474}
475
476/// API key rotation response.
477#[derive(Serialize)]
478pub struct ApiKeyRotationResponse {
479    pub old_key_id: String,
480    pub new_key: ApiKeyResponse,
481}
482
483/// Statute comparison response.
484#[derive(Serialize)]
485pub struct StatuteComparisonResponse {
486    pub statute_a: StatuteSummary,
487    pub statute_b: StatuteSummary,
488    pub differences: ComparisonDifferences,
489    pub similarity_score: f64,
490}
491
492/// Differences between two statutes.
493#[derive(Serialize)]
494pub struct ComparisonDifferences {
495    pub precondition_count_diff: i32,
496    pub nesting_depth_diff: i32,
497    pub both_have_discretion: bool,
498    pub discretion_differs: bool,
499}
500
501/// Batch statute create request.
502#[derive(Deserialize)]
503pub struct BatchCreateStatutesRequest {
504    pub statutes: Vec<Statute>,
505}
506
507/// Batch statute create response.
508#[derive(Serialize)]
509pub struct BatchCreateStatutesResponse {
510    pub created: usize,
511    pub failed: usize,
512    pub errors: Vec<String>,
513}
514
515/// Batch statute delete request.
516#[derive(Deserialize)]
517pub struct BatchDeleteStatutesRequest {
518    pub statute_ids: Vec<String>,
519}
520
521/// Batch statute delete response.
522#[derive(Serialize)]
523pub struct BatchDeleteStatutesResponse {
524    pub deleted: usize,
525    pub not_found: Vec<String>,
526}
527
528/// Create new version of statute request.
529#[derive(Deserialize)]
530pub struct CreateVersionRequest {
531    /// Optional modifications to apply to the new version
532    pub title: Option<String>,
533    pub preconditions: Option<Vec<legalis_core::Condition>>,
534    pub effect: Option<legalis_core::Effect>,
535    pub discretion_logic: Option<String>,
536}
537
538/// Statute version list response.
539#[derive(Serialize)]
540pub struct StatuteVersionListResponse {
541    pub base_id: String,
542    pub versions: Vec<StatuteVersionInfo>,
543    pub total_versions: usize,
544}
545
546/// Information about a statute version.
547#[derive(Serialize)]
548pub struct StatuteVersionInfo {
549    pub id: String,
550    pub version: u32,
551    pub title: String,
552    pub created_at: Option<String>,
553}
554
555/// Conflict detection request.
556#[derive(Deserialize)]
557pub struct ConflictDetectionRequest {
558    pub statute_ids: Vec<String>,
559}
560
561/// Conflict detection response.
562#[derive(Serialize)]
563pub struct ConflictDetectionResponse {
564    pub conflicts: Vec<ConflictInfo>,
565    pub conflict_count: usize,
566}
567
568/// Information about a detected conflict.
569#[derive(Serialize)]
570pub struct ConflictInfo {
571    pub statute_a_id: String,
572    pub statute_b_id: String,
573    pub conflict_type: String,
574    pub description: String,
575}
576
577/// Simulation request.
578#[derive(Deserialize)]
579pub struct SimulationRequest {
580    pub statute_ids: Vec<String>,
581    pub population_size: usize,
582    pub entity_params: std::collections::HashMap<String, String>,
583}
584
585/// Simulation response.
586#[derive(Serialize, Deserialize)]
587pub struct SimulationResponse {
588    pub simulation_id: String,
589    pub total_entities: usize,
590    pub deterministic_outcomes: usize,
591    pub discretionary_outcomes: usize,
592    pub void_outcomes: usize,
593    pub deterministic_rate: f64,
594    pub discretionary_rate: f64,
595    pub void_rate: f64,
596    pub completed_at: String,
597}
598
599/// Simulation comparison request.
600#[derive(Deserialize)]
601pub struct SimulationComparisonRequest {
602    pub statute_ids_a: Vec<String>,
603    pub statute_ids_b: Vec<String>,
604    pub population_size: usize,
605}
606
607/// Simulation comparison response.
608#[derive(Serialize)]
609pub struct SimulationComparisonResponse {
610    pub scenario_a: SimulationScenarioResult,
611    pub scenario_b: SimulationScenarioResult,
612    pub differences: SimulationDifferences,
613}
614
615/// Save simulation request.
616#[derive(Deserialize)]
617pub struct SaveSimulationRequest {
618    pub name: String,
619    pub description: Option<String>,
620    pub simulation_result: SimulationResponse,
621}
622
623/// Compliance check request.
624#[derive(Deserialize)]
625pub struct ComplianceCheckRequest {
626    pub statute_ids: Vec<String>,
627    pub entity_attributes: std::collections::HashMap<String, String>,
628}
629
630/// Compliance check response.
631#[derive(Serialize)]
632pub struct ComplianceCheckResponse {
633    pub compliant: bool,
634    pub requires_discretion: bool,
635    pub not_applicable: bool,
636    pub applicable_statutes: Vec<String>,
637    pub checked_statute_count: usize,
638}
639
640/// What-if analysis request.
641#[derive(Deserialize)]
642pub struct WhatIfRequest {
643    pub statute_ids: Vec<String>,
644    pub baseline_attributes: std::collections::HashMap<String, String>,
645    pub modified_attributes: std::collections::HashMap<String, String>,
646}
647
648/// What-if analysis response.
649#[derive(Serialize)]
650pub struct WhatIfResponse {
651    pub baseline_compliant: bool,
652    pub modified_compliant: bool,
653    pub impact: String,
654    pub baseline_requires_discretion: bool,
655    pub modified_requires_discretion: bool,
656    pub changed_attribute_count: usize,
657}
658
659/// List saved simulations query.
660#[derive(Deserialize)]
661pub struct ListSavedSimulationsQuery {
662    pub limit: Option<usize>,
663    pub offset: Option<usize>,
664}
665
666/// Visualization format options.
667#[derive(Debug, Clone, Copy, Deserialize)]
668#[serde(rename_all = "lowercase")]
669#[derive(Default)]
670pub enum VizFormat {
671    Dot,
672    Ascii,
673    Mermaid,
674    PlantUml,
675    #[default]
676    Svg,
677    Html,
678}
679
680/// Visualization request query parameters.
681#[derive(Deserialize)]
682pub struct VizQuery {
683    /// Output format
684    #[serde(default)]
685    pub format: VizFormat,
686    /// Theme (light, dark, high_contrast, colorblind_friendly)
687    pub theme: Option<String>,
688}
689
690/// Visualization response.
691#[derive(Serialize)]
692pub struct VisualizationResponse {
693    pub statute_id: String,
694    pub format: String,
695    pub content: String,
696    pub node_count: usize,
697    pub discretionary_count: usize,
698}
699
700/// Results for a single simulation scenario.
701#[derive(Serialize)]
702pub struct SimulationScenarioResult {
703    pub name: String,
704    pub deterministic_rate: f64,
705    pub discretionary_rate: f64,
706    pub void_rate: f64,
707}
708
709/// Differences between two simulation scenarios.
710#[derive(Serialize)]
711pub struct SimulationDifferences {
712    pub deterministic_diff: f64,
713    pub discretionary_diff: f64,
714    pub void_diff: f64,
715    pub significant_change: bool,
716}
717
718/// Get permissions for a specific statute.
719async fn get_statute_permissions(
720    user: auth::AuthUser,
721    State(state): State<Arc<AppState>>,
722    Path(statute_id): Path<String>,
723) -> Result<impl IntoResponse, ApiError> {
724    user.require_permission(auth::Permission::ReadStatutes)?;
725
726    // Check if statute exists
727    let statutes = state.statutes.read().await;
728    if !statutes.iter().any(|s| s.id == statute_id) {
729        return Err(ApiError::NotFound(format!(
730            "Statute not found: {}",
731            statute_id
732        )));
733    }
734    drop(statutes);
735
736    // Get all users who have access to this statute
737    // This is a simplified version - in production, you'd have a way to iterate
738    // through users or store reverse mappings via the ReBAC engine
739    // For now, we'll return a placeholder response
740    let permissions_list = vec![StatutePermissionEntry {
741        user_id: "system".to_string(),
742        permission: "owner".to_string(),
743    }];
744
745    Ok(Json(ApiResponse::new(StatutePermissionsResponse {
746        statute_id,
747        permissions: permissions_list,
748    })))
749}
750
751/// Grant permission on a statute to a user.
752async fn grant_statute_permission(
753    user: auth::AuthUser,
754    State(state): State<Arc<AppState>>,
755    Path(statute_id): Path<String>,
756    Json(req): Json<StatutePermissionRequest>,
757) -> Result<impl IntoResponse, ApiError> {
758    user.require_permission(auth::Permission::ManageUsers)?;
759
760    // Check if statute exists
761    let statutes = state.statutes.read().await;
762    if !statutes.iter().any(|s| s.id == statute_id) {
763        return Err(ApiError::NotFound(format!(
764            "Statute not found: {}",
765            statute_id
766        )));
767    }
768    drop(statutes);
769
770    // Parse user ID
771    let target_user_id = uuid::Uuid::parse_str(&req.user_id)
772        .map_err(|_| ApiError::BadRequest("Invalid user ID format".to_string()))?;
773
774    // Create a deterministic UUID from statute_id using hash
775    use std::hash::{Hash, Hasher};
776    let mut hasher = std::collections::hash_map::DefaultHasher::new();
777    statute_id.hash(&mut hasher);
778    let hash_value = hasher.finish();
779
780    // Convert hash to UUID (deterministic based on statute_id)
781    let resource_uuid = uuid::Uuid::from_u128(hash_value as u128);
782
783    // Parse relation type
784    let relation = match req.permission.as_str() {
785        "owner" => rebac::Relation::Owner,
786        "editor" => rebac::Relation::Editor,
787        "viewer" => rebac::Relation::Viewer,
788        _ => {
789            return Err(ApiError::BadRequest(format!(
790                "Invalid permission type: {}. Must be one of: owner, editor, viewer",
791                req.permission
792            )));
793        }
794    };
795
796    let mut rebac = state.rebac.write().await;
797
798    // Grant the permission via ReBAC
799    let tuple = rebac::RelationTuple::new(
800        target_user_id,
801        relation,
802        rebac::ResourceType::Statute,
803        resource_uuid,
804    );
805    rebac.add_tuple(tuple);
806
807    // Update metrics
808    metrics::PERMISSION_OPERATIONS
809        .with_label_values(&["grant"])
810        .inc();
811
812    // Audit log
813    state
814        .audit_log
815        .log_success(
816            audit::AuditEventType::PermissionGranted,
817            user.id.to_string(),
818            user.username.clone(),
819            "grant_statute_permission".to_string(),
820            Some(statute_id.clone()),
821            Some("statute".to_string()),
822            serde_json::json!({
823                "statute_id": statute_id,
824                "granted_to": req.user_id,
825                "permission": req.permission
826            }),
827        )
828        .await;
829
830    Ok((
831        StatusCode::OK,
832        Json(ApiResponse::new(serde_json::json!({
833            "message": "Permission granted successfully",
834            "statute_id": statute_id,
835            "user_id": req.user_id,
836            "permission": req.permission
837        }))),
838    ))
839}
840
841/// Revoke permission on a statute from a user.
842async fn revoke_statute_permission(
843    user: auth::AuthUser,
844    State(state): State<Arc<AppState>>,
845    Path(statute_id): Path<String>,
846    Json(req): Json<StatutePermissionRequest>,
847) -> Result<impl IntoResponse, ApiError> {
848    user.require_permission(auth::Permission::ManageUsers)?;
849
850    // Check if statute exists
851    let statutes = state.statutes.read().await;
852    if !statutes.iter().any(|s| s.id == statute_id) {
853        return Err(ApiError::NotFound(format!(
854            "Statute not found: {}",
855            statute_id
856        )));
857    }
858    drop(statutes);
859
860    // Parse user ID
861    let target_user_id = uuid::Uuid::parse_str(&req.user_id)
862        .map_err(|_| ApiError::BadRequest("Invalid user ID format".to_string()))?;
863
864    // Create a deterministic UUID from statute_id using hash
865    use std::hash::{Hash, Hasher};
866    let mut hasher = std::collections::hash_map::DefaultHasher::new();
867    statute_id.hash(&mut hasher);
868    let hash_value = hasher.finish();
869
870    // Convert hash to UUID (deterministic based on statute_id)
871    let resource_uuid = uuid::Uuid::from_u128(hash_value as u128);
872
873    // Parse relation type
874    let relation = match req.permission.as_str() {
875        "owner" => rebac::Relation::Owner,
876        "editor" => rebac::Relation::Editor,
877        "viewer" => rebac::Relation::Viewer,
878        _ => {
879            return Err(ApiError::BadRequest(format!(
880                "Invalid permission type: {}. Must be one of: owner, editor, viewer",
881                req.permission
882            )));
883        }
884    };
885
886    let mut rebac = state.rebac.write().await;
887
888    // Revoke the permission via ReBAC
889    let tuple = rebac::RelationTuple::new(
890        target_user_id,
891        relation,
892        rebac::ResourceType::Statute,
893        resource_uuid,
894    );
895    rebac.remove_tuple(&tuple);
896
897    // Update metrics
898    metrics::PERMISSION_OPERATIONS
899        .with_label_values(&["revoke"])
900        .inc();
901
902    // Audit log
903    state
904        .audit_log
905        .log_success(
906            audit::AuditEventType::PermissionRevoked,
907            user.id.to_string(),
908            user.username.clone(),
909            "revoke_statute_permission".to_string(),
910            Some(statute_id.clone()),
911            Some("statute".to_string()),
912            serde_json::json!({
913                "statute_id": statute_id,
914                "revoked_from": req.user_id,
915                "permission": req.permission
916            }),
917        )
918        .await;
919
920    Ok((
921        StatusCode::OK,
922        Json(ApiResponse::new(serde_json::json!({
923            "message": "Permission revoked successfully",
924            "statute_id": statute_id,
925            "user_id": req.user_id,
926            "permission": req.permission
927        }))),
928    ))
929}
930
931/// Create a new API key.
932async fn create_api_key(
933    user: auth::AuthUser,
934    State(state): State<Arc<AppState>>,
935    Json(req): Json<CreateApiKeyRequest>,
936) -> Result<impl IntoResponse, ApiError> {
937    user.require_permission(auth::Permission::ManageApiKeys)?;
938
939    // Parse scopes if provided
940    let scopes = if let Some(scope_strs) = req.scopes {
941        let mut parsed_scopes = std::collections::HashSet::new();
942        for scope_str in scope_strs {
943            let permission = match scope_str.as_str() {
944                "read_statutes" => auth::Permission::ReadStatutes,
945                "create_statutes" => auth::Permission::CreateStatutes,
946                "update_statutes" => auth::Permission::UpdateStatutes,
947                "delete_statutes" => auth::Permission::DeleteStatutes,
948                "verify_statutes" => auth::Permission::VerifyStatutes,
949                "run_simulations" => auth::Permission::RunSimulations,
950                "view_analytics" => auth::Permission::ViewAnalytics,
951                "manage_users" => auth::Permission::ManageUsers,
952                "manage_api_keys" => auth::Permission::ManageApiKeys,
953                "admin" => auth::Permission::Admin,
954                _ => {
955                    return Err(ApiError::BadRequest(format!(
956                        "Invalid permission: {}",
957                        scope_str
958                    )));
959                }
960            };
961            parsed_scopes.insert(permission);
962        }
963        parsed_scopes
964    } else {
965        req.role.permissions()
966    };
967
968    // Create API key
969    let api_key = if let Some(expires_in_days) = req.expires_in_days {
970        auth::ApiKey::with_expiration(req.name, user.id, req.role, expires_in_days)
971    } else {
972        auth::ApiKey::with_scopes(req.name, user.id, req.role, scopes)
973    };
974
975    let key_id = api_key.id.to_string();
976    let key_value = api_key.key.clone();
977
978    // Store API key
979    let mut api_keys = state.api_keys.write().await;
980    api_keys.push(api_key.clone());
981    drop(api_keys);
982
983    // Audit log
984    state
985        .audit_log
986        .log_success(
987            audit::AuditEventType::ApiKeyCreated,
988            user.id.to_string(),
989            user.username.clone(),
990            "create_api_key".to_string(),
991            Some(key_id.clone()),
992            Some("api_key".to_string()),
993            serde_json::json!({
994                "key_id": key_id,
995                "name": api_key.name,
996                "role": format!("{:?}", api_key.role)
997            }),
998        )
999        .await;
1000
1001    let response = ApiKeyResponse {
1002        id: key_id,
1003        key: Some(key_value), // Only shown on creation
1004        name: api_key.name,
1005        role: format!("{:?}", api_key.role),
1006        scopes: api_key.scopes.iter().map(|s| format!("{:?}", s)).collect(),
1007        created_at: chrono::DateTime::from_timestamp(api_key.created_at, 0)
1008            .unwrap_or_default()
1009            .to_rfc3339(),
1010        expires_at: api_key.expires_at.map(|ts| {
1011            chrono::DateTime::from_timestamp(ts, 0)
1012                .unwrap_or_default()
1013                .to_rfc3339()
1014        }),
1015        active: api_key.active,
1016        last_used_at: None,
1017    };
1018
1019    Ok((StatusCode::CREATED, Json(ApiResponse::new(response))))
1020}
1021
1022/// List all API keys for the current user.
1023async fn list_api_keys(
1024    user: auth::AuthUser,
1025    State(state): State<Arc<AppState>>,
1026) -> Result<impl IntoResponse, ApiError> {
1027    user.require_permission(auth::Permission::ManageApiKeys)?;
1028
1029    let api_keys = state.api_keys.read().await;
1030
1031    let keys: Vec<ApiKeyResponse> = api_keys
1032        .iter()
1033        .filter(|key| key.owner_id == user.id || user.has_permission(auth::Permission::Admin))
1034        .map(|key| ApiKeyResponse {
1035            id: key.id.to_string(),
1036            key: None, // Never show the key value in list
1037            name: key.name.clone(),
1038            role: format!("{:?}", key.role),
1039            scopes: key.scopes.iter().map(|s| format!("{:?}", s)).collect(),
1040            created_at: chrono::DateTime::from_timestamp(key.created_at, 0)
1041                .unwrap_or_default()
1042                .to_rfc3339(),
1043            expires_at: key.expires_at.map(|ts| {
1044                chrono::DateTime::from_timestamp(ts, 0)
1045                    .unwrap_or_default()
1046                    .to_rfc3339()
1047            }),
1048            active: key.active,
1049            last_used_at: key.last_used_at.map(|ts| {
1050                chrono::DateTime::from_timestamp(ts, 0)
1051                    .unwrap_or_default()
1052                    .to_rfc3339()
1053            }),
1054        })
1055        .collect();
1056
1057    Ok(Json(ApiResponse::new(ApiKeyListResponse { keys })))
1058}
1059
1060/// Get a specific API key.
1061#[allow(dead_code)]
1062async fn get_api_key(
1063    user: auth::AuthUser,
1064    State(state): State<Arc<AppState>>,
1065    Path(id): Path<String>,
1066) -> Result<impl IntoResponse, ApiError> {
1067    user.require_permission(auth::Permission::ManageApiKeys)?;
1068
1069    let key_id = uuid::Uuid::parse_str(&id)
1070        .map_err(|_| ApiError::BadRequest("Invalid key ID format".to_string()))?;
1071
1072    let api_keys = state.api_keys.read().await;
1073
1074    let key = api_keys
1075        .iter()
1076        .find(|k| {
1077            k.id == key_id
1078                && (k.owner_id == user.id || user.has_permission(auth::Permission::Admin))
1079        })
1080        .ok_or_else(|| ApiError::NotFound("API key not found".to_string()))?;
1081
1082    let response = ApiKeyResponse {
1083        id: key.id.to_string(),
1084        key: None, // Never show the key value
1085        name: key.name.clone(),
1086        role: format!("{:?}", key.role),
1087        scopes: key.scopes.iter().map(|s| format!("{:?}", s)).collect(),
1088        created_at: chrono::DateTime::from_timestamp(key.created_at, 0)
1089            .unwrap_or_default()
1090            .to_rfc3339(),
1091        expires_at: key.expires_at.map(|ts| {
1092            chrono::DateTime::from_timestamp(ts, 0)
1093                .unwrap_or_default()
1094                .to_rfc3339()
1095        }),
1096        active: key.active,
1097        last_used_at: key.last_used_at.map(|ts| {
1098            chrono::DateTime::from_timestamp(ts, 0)
1099                .unwrap_or_default()
1100                .to_rfc3339()
1101        }),
1102    };
1103
1104    Ok(Json(ApiResponse::new(response)))
1105}
1106
1107/// Revoke an API key.
1108async fn revoke_api_key(
1109    user: auth::AuthUser,
1110    State(state): State<Arc<AppState>>,
1111    Path(id): Path<String>,
1112) -> Result<impl IntoResponse, ApiError> {
1113    user.require_permission(auth::Permission::ManageApiKeys)?;
1114
1115    let key_id = uuid::Uuid::parse_str(&id)
1116        .map_err(|_| ApiError::BadRequest("Invalid key ID format".to_string()))?;
1117
1118    let mut api_keys = state.api_keys.write().await;
1119
1120    let key_index = api_keys
1121        .iter()
1122        .position(|k| {
1123            k.id == key_id
1124                && (k.owner_id == user.id || user.has_permission(auth::Permission::Admin))
1125        })
1126        .ok_or_else(|| ApiError::NotFound("API key not found".to_string()))?;
1127
1128    let key = api_keys.remove(key_index);
1129
1130    drop(api_keys);
1131
1132    // Audit log
1133    state
1134        .audit_log
1135        .log_success(
1136            audit::AuditEventType::ApiKeyRevoked,
1137            user.id.to_string(),
1138            user.username.clone(),
1139            "revoke_api_key".to_string(),
1140            Some(key.id.to_string()),
1141            Some("api_key".to_string()),
1142            serde_json::json!({
1143                "key_id": key.id.to_string(),
1144                "name": key.name
1145            }),
1146        )
1147        .await;
1148
1149    Ok((
1150        StatusCode::OK,
1151        Json(ApiResponse::new(serde_json::json!({
1152            "message": "API key revoked successfully",
1153            "key_id": key.id.to_string()
1154        }))),
1155    ))
1156}
1157
1158/// Rotate an API key (creates a new key, deactivates the old one).
1159async fn rotate_api_key(
1160    user: auth::AuthUser,
1161    State(state): State<Arc<AppState>>,
1162    Path(id): Path<String>,
1163) -> Result<impl IntoResponse, ApiError> {
1164    user.require_permission(auth::Permission::ManageApiKeys)?;
1165
1166    let key_id = uuid::Uuid::parse_str(&id)
1167        .map_err(|_| ApiError::BadRequest("Invalid key ID format".to_string()))?;
1168
1169    let mut api_keys = state.api_keys.write().await;
1170
1171    let old_key = api_keys
1172        .iter_mut()
1173        .find(|k| {
1174            k.id == key_id
1175                && (k.owner_id == user.id || user.has_permission(auth::Permission::Admin))
1176        })
1177        .ok_or_else(|| ApiError::NotFound("API key not found".to_string()))?;
1178
1179    // Create new key
1180    let new_key = old_key.rotate();
1181    let new_key_value = new_key.key.clone();
1182
1183    // Deactivate old key
1184    old_key.active = false;
1185
1186    // Add new key
1187    api_keys.push(new_key.clone());
1188    drop(api_keys);
1189
1190    // Audit log
1191    state
1192        .audit_log
1193        .log_success(
1194            audit::AuditEventType::ApiKeyRotated,
1195            user.id.to_string(),
1196            user.username.clone(),
1197            "rotate_api_key".to_string(),
1198            Some(new_key.id.to_string()),
1199            Some("api_key".to_string()),
1200            serde_json::json!({
1201                "old_key_id": key_id.to_string(),
1202                "new_key_id": new_key.id.to_string()
1203            }),
1204        )
1205        .await;
1206
1207    let response = ApiKeyRotationResponse {
1208        old_key_id: key_id.to_string(),
1209        new_key: ApiKeyResponse {
1210            id: new_key.id.to_string(),
1211            key: Some(new_key_value), // Only shown on rotation
1212            name: new_key.name,
1213            role: format!("{:?}", new_key.role),
1214            scopes: new_key.scopes.iter().map(|s| format!("{:?}", s)).collect(),
1215            created_at: chrono::DateTime::from_timestamp(new_key.created_at, 0)
1216                .unwrap_or_default()
1217                .to_rfc3339(),
1218            expires_at: new_key.expires_at.map(|ts| {
1219                chrono::DateTime::from_timestamp(ts, 0)
1220                    .unwrap_or_default()
1221                    .to_rfc3339()
1222            }),
1223            active: new_key.active,
1224            last_used_at: None,
1225        },
1226    };
1227
1228    Ok((StatusCode::OK, Json(ApiResponse::new(response))))
1229}
1230
1231/// Query audit logs with filtering.
1232async fn query_audit_logs(
1233    user: auth::AuthUser,
1234    State(state): State<Arc<AppState>>,
1235    Query(filter): Query<audit::AuditQueryFilter>,
1236) -> Result<impl IntoResponse, ApiError> {
1237    // Only admins can view audit logs
1238    user.require_permission(auth::Permission::Admin)?;
1239
1240    let entries = state.audit_log.query(filter.clone()).await;
1241    let total = state.audit_log.count_filtered(filter).await;
1242
1243    let meta = ResponseMeta {
1244        total: Some(total),
1245        ..Default::default()
1246    };
1247
1248    Ok(Json(ApiResponse::new(entries).with_meta(meta)))
1249}
1250
1251/// Get audit log statistics.
1252async fn audit_stats(
1253    user: auth::AuthUser,
1254    State(state): State<Arc<AppState>>,
1255) -> Result<impl IntoResponse, ApiError> {
1256    // Only admins can view audit stats
1257    user.require_permission(auth::Permission::Admin)?;
1258
1259    let total_count = state.audit_log.count().await;
1260
1261    // Count by event type
1262    let statute_created = state
1263        .audit_log
1264        .count_filtered(audit::AuditQueryFilter {
1265            event_type: Some(audit::AuditEventType::StatuteCreated),
1266            ..Default::default()
1267        })
1268        .await;
1269
1270    let statute_deleted = state
1271        .audit_log
1272        .count_filtered(audit::AuditQueryFilter {
1273            event_type: Some(audit::AuditEventType::StatuteDeleted),
1274            ..Default::default()
1275        })
1276        .await;
1277
1278    let simulations_saved = state
1279        .audit_log
1280        .count_filtered(audit::AuditQueryFilter {
1281            event_type: Some(audit::AuditEventType::SimulationSaved),
1282            ..Default::default()
1283        })
1284        .await;
1285
1286    let stats = serde_json::json!({
1287        "total_audit_entries": total_count,
1288        "by_event_type": {
1289            "statute_created": statute_created,
1290            "statute_deleted": statute_deleted,
1291            "simulations_saved": simulations_saved
1292        }
1293    });
1294
1295    Ok(Json(ApiResponse::new(stats)))
1296}
1297
1298/// GraphQL handler.
1299async fn graphql_handler(
1300    schema: axum::extract::Extension<graphql::LegalisSchema>,
1301    req: async_graphql_axum::GraphQLRequest,
1302) -> async_graphql_axum::GraphQLResponse {
1303    schema.execute(req.into_inner()).await.into()
1304}
1305
1306/// GraphQL playground handler.
1307async fn graphql_playground() -> impl IntoResponse {
1308    axum::response::Html(async_graphql::http::playground_source(
1309        async_graphql::http::GraphQLPlaygroundConfig::new("/graphql"),
1310    ))
1311}
1312
1313/// Creates the API router.
1314pub fn create_router(state: Arc<AppState>) -> Router {
1315    // Initialize metrics
1316    metrics::init();
1317
1318    // Create GraphQL schema with shared WebSocket broadcaster
1319    let graphql_state = graphql::GraphQLState::with_broadcaster(state.ws_broadcaster.clone());
1320    let graphql_schema = graphql::create_schema(graphql_state);
1321
1322    Router::new()
1323        .route("/health", get(health_check))
1324        .route("/health/ready", get(readiness_check))
1325        .route("/metrics", get(metrics_endpoint))
1326        .route("/api/v1/statutes", get(list_statutes).post(create_statute))
1327        .route("/api/v1/statutes/search", get(search_statutes))
1328        .route("/api/v1/statutes/suggest", post(suggest_statutes))
1329        .route("/api/v1/statutes/batch", post(batch_create_statutes))
1330        .route("/api/v1/statutes/batch/delete", post(batch_delete_statutes))
1331        .route("/api/v1/statutes/compare", post(compare_statutes))
1332        .route(
1333            "/api/v1/statutes/compare/matrix",
1334            post(compare_statutes_matrix),
1335        )
1336        .route(
1337            "/api/v1/statutes/{id}",
1338            get(get_statute).delete(delete_statute),
1339        )
1340        .route("/api/v1/statutes/{id}/complexity", get(analyze_complexity))
1341        .route("/api/v1/statutes/{id}/versions", get(get_statute_versions))
1342        .route(
1343            "/api/v1/statutes/{id}/versions/new",
1344            post(create_statute_version),
1345        )
1346        .route("/api/v1/verify", post(verify_statutes))
1347        .route("/api/v1/verify/detailed", post(verify_statutes_detailed))
1348        .route("/api/v1/verify/conflicts", post(detect_conflicts))
1349        .route("/api/v1/verify/batch", post(verify_batch))
1350        .route("/api/v1/verify/bulk/stream", post(verify_bulk_stream))
1351        .route("/api/v1/verify/async", post(verify_statutes_async))
1352        .route(
1353            "/api/v1/verify/async/{job_id}",
1354            get(get_verification_job_status),
1355        )
1356        .route("/api/v1/simulate", post(run_simulation))
1357        .route("/api/v1/simulate/stream", post(stream_simulation))
1358        .route("/api/v1/simulate/compare", post(compare_simulations))
1359        .route("/api/v1/simulate/compliance", post(check_compliance))
1360        .route("/api/v1/simulate/whatif", post(whatif_analysis))
1361        .route(
1362            "/api/v1/simulate/saved",
1363            get(list_saved_simulations).post(save_simulation),
1364        )
1365        .route(
1366            "/api/v1/simulate/saved/{id}",
1367            get(get_saved_simulation).delete(delete_saved_simulation),
1368        )
1369        .route("/api/v1/visualize/{id}", get(visualize_statute))
1370        .route("/api-docs/openapi.json", get(openapi_spec))
1371        .route("/api-docs", get(swagger_ui))
1372        .route("/graphql", post(graphql_handler))
1373        .route("/graphql/playground", get(graphql_playground))
1374        .route("/ws", get(websocket::ws_handler))
1375        .route("/api/v1/audit", get(query_audit_logs))
1376        .route("/api/v1/audit/stats", get(audit_stats))
1377        .route(
1378            "/api/v1/statutes/{id}/permissions",
1379            get(get_statute_permissions)
1380                .post(grant_statute_permission)
1381                .delete(revoke_statute_permission),
1382        )
1383        .route("/api/v1/api-keys", get(list_api_keys).post(create_api_key))
1384        .route(
1385            "/api/v1/api-keys/{id}",
1386            get(get_api_key).delete(revoke_api_key),
1387        )
1388        .route("/api/v1/api-keys/{id}/rotate", post(rotate_api_key))
1389        .layer(Extension(graphql_schema))
1390        .layer(middleware::from_fn(logging::log_request))
1391        .layer(CompressionLayer::new())
1392        .layer(CorsLayer::permissive())
1393        .with_state(state)
1394}
1395
1396/// Returns the OpenAPI 3.0 specification.
1397async fn openapi_spec() -> impl IntoResponse {
1398    Json(openapi::generate_spec())
1399}
1400
1401/// Returns the Swagger UI HTML page.
1402async fn swagger_ui() -> impl IntoResponse {
1403    axum::response::Html(openapi::generate_swagger_ui_html())
1404}
1405
1406/// Health check endpoint - liveness probe.
1407async fn health_check() -> impl IntoResponse {
1408    Json(serde_json::json!({
1409        "status": "healthy",
1410        "service": "legalis-api",
1411        "version": env!("CARGO_PKG_VERSION"),
1412        "timestamp": chrono::Utc::now().to_rfc3339()
1413    }))
1414}
1415
1416/// Readiness check endpoint - checks if the service is ready to accept requests.
1417async fn readiness_check(
1418    State(state): State<Arc<AppState>>,
1419) -> Result<impl IntoResponse, ApiError> {
1420    // Check if we can access the statutes (test read lock)
1421    let statutes_available = state.statutes.try_read().is_ok();
1422    let rebac_available = state.rebac.try_read().is_ok();
1423
1424    let is_ready = statutes_available && rebac_available;
1425
1426    let response = serde_json::json!({
1427        "status": if is_ready { "ready" } else { "not_ready" },
1428        "service": "legalis-api",
1429        "version": env!("CARGO_PKG_VERSION"),
1430        "timestamp": chrono::Utc::now().to_rfc3339(),
1431        "checks": {
1432            "statutes_store": if statutes_available { "ok" } else { "unavailable" },
1433            "rebac_engine": if rebac_available { "ok" } else { "unavailable" }
1434        }
1435    });
1436
1437    if is_ready {
1438        Ok(Json(response))
1439    } else {
1440        Err(ApiError::Internal("Service not ready".to_string()))
1441    }
1442}
1443
1444/// Prometheus metrics endpoint.
1445async fn metrics_endpoint() -> Result<String, ApiError> {
1446    metrics::encode().map_err(|e| ApiError::Internal(format!("Failed to encode metrics: {}", e)))
1447}
1448
1449/// List all statutes.
1450async fn list_statutes(
1451    user: auth::AuthUser,
1452    State(state): State<Arc<AppState>>,
1453) -> Result<impl IntoResponse, ApiError> {
1454    user.require_permission(auth::Permission::ReadStatutes)?;
1455
1456    let statutes = state.statutes.read().await;
1457    let summaries: Vec<StatuteSummary> = statutes.iter().map(StatuteSummary::from).collect();
1458
1459    Ok(Json(ApiResponse::new(StatuteListResponse {
1460        statutes: summaries,
1461    })))
1462}
1463
1464/// Search/filter statutes.
1465async fn search_statutes(
1466    user: auth::AuthUser,
1467    State(state): State<Arc<AppState>>,
1468    Query(query): Query<StatuteSearchQuery>,
1469) -> Result<impl IntoResponse, ApiError> {
1470    user.require_permission(auth::Permission::ReadStatutes)?;
1471
1472    // Parse field selection
1473    let _field_query = field_selection::FieldsQuery {
1474        fields: query.fields.clone(),
1475    };
1476
1477    let statutes = state.statutes.read().await;
1478
1479    let mut filtered: Vec<&Statute> = statutes.iter().collect();
1480
1481    // Filter by title
1482    if let Some(ref title_query) = query.title {
1483        let title_lower = title_query.to_lowercase();
1484        filtered.retain(|s| s.title.to_lowercase().contains(&title_lower));
1485    }
1486
1487    // Filter by discretion
1488    if let Some(has_discretion) = query.has_discretion {
1489        filtered.retain(|s| s.discretion_logic.is_some() == has_discretion);
1490    }
1491
1492    // Filter by min preconditions
1493    if let Some(min) = query.min_preconditions {
1494        filtered.retain(|s| s.preconditions.len() >= min);
1495    }
1496
1497    // Filter by max preconditions
1498    if let Some(max) = query.max_preconditions {
1499        filtered.retain(|s| s.preconditions.len() <= max);
1500    }
1501
1502    let total = filtered.len();
1503
1504    // Support both cursor-based and offset-based pagination
1505    let (paginated, meta) = if let Some(cursor) = query.cursor {
1506        // Cursor-based pagination
1507        let limit = query.limit.unwrap_or(100).min(1000);
1508
1509        // Decode cursor (format: base64(id:version))
1510        let cursor_decoded = base64_decode(&cursor)
1511            .map_err(|_| ApiError::BadRequest("Invalid cursor".to_string()))?;
1512
1513        let cursor_parts: Vec<&str> = cursor_decoded.split(':').collect();
1514        if cursor_parts.len() != 2 {
1515            return Err(ApiError::BadRequest("Invalid cursor format".to_string()));
1516        }
1517
1518        let cursor_id = cursor_parts[0];
1519        let cursor_version: u32 = cursor_parts[1]
1520            .parse()
1521            .map_err(|_| ApiError::BadRequest("Invalid cursor version".to_string()))?;
1522
1523        // Find position of cursor
1524        let cursor_pos = filtered
1525            .iter()
1526            .position(|s| s.id == cursor_id && s.version == cursor_version);
1527
1528        let start_pos = cursor_pos.map(|p| p + 1).unwrap_or(0);
1529
1530        let results: Vec<StatuteSummary> = filtered
1531            .iter()
1532            .skip(start_pos)
1533            .take(limit + 1) // Take one extra to check if there are more
1534            .map(|s| StatuteSummary::from(*s))
1535            .collect();
1536
1537        let has_more = results.len() > limit;
1538        let mut final_results = results;
1539        if has_more {
1540            final_results.pop(); // Remove the extra item
1541        }
1542
1543        // Generate next cursor if there are more results
1544        let next_cursor = if has_more && !final_results.is_empty() {
1545            let last = &final_results[final_results.len() - 1];
1546            Some(base64_encode(&format!("{}:{}", last.id, 1))) // Use version 1 as default
1547        } else {
1548            None
1549        };
1550
1551        let meta = ResponseMeta {
1552            total: Some(total),
1553            next_cursor,
1554            has_more: Some(has_more),
1555            ..Default::default()
1556        };
1557
1558        (final_results, meta)
1559    } else {
1560        // Offset-based pagination
1561        let offset = query.offset.unwrap_or(0);
1562        let limit = query.limit.unwrap_or(100).min(1000);
1563
1564        let paginated = filtered
1565            .into_iter()
1566            .skip(offset)
1567            .take(limit)
1568            .map(StatuteSummary::from)
1569            .collect();
1570
1571        let meta = ResponseMeta {
1572            total: Some(total),
1573            page: Some(offset / limit),
1574            per_page: Some(limit),
1575            ..Default::default()
1576        };
1577
1578        (paginated, meta)
1579    };
1580
1581    Ok(Json(
1582        ApiResponse::new(StatuteListResponse {
1583            statutes: paginated,
1584        })
1585        .with_meta(meta),
1586    ))
1587}
1588
1589/// AI-powered statute suggestion endpoint.
1590async fn suggest_statutes(
1591    user: auth::AuthUser,
1592    State(state): State<Arc<AppState>>,
1593    Json(request): Json<ai_suggestions::SuggestionRequest>,
1594) -> Result<impl IntoResponse, ApiError> {
1595    user.require_permission(auth::Permission::ReadStatutes)?;
1596
1597    // Get available statutes
1598    let statutes = state.statutes.read().await;
1599    let statute_vec: Vec<_> = statutes.iter().cloned().collect();
1600
1601    // Create suggestion engine (without LLM provider for now, uses rule-based)
1602    let engine = ai_suggestions::SuggestionEngine::new();
1603
1604    // Generate suggestions
1605    let response = engine
1606        .suggest(request, &statute_vec)
1607        .await
1608        .map_err(|e| ApiError::Internal(format!("Suggestion failed: {}", e)))?;
1609
1610    Ok(Json(ApiResponse::new(response)))
1611}
1612
1613/// Base64 encode a string.
1614fn base64_encode(s: &str) -> String {
1615    use base64::{Engine as _, engine::general_purpose};
1616    general_purpose::STANDARD.encode(s)
1617}
1618
1619/// Base64 decode a string.
1620fn base64_decode(s: &str) -> Result<String, base64::DecodeError> {
1621    use base64::{Engine as _, engine::general_purpose};
1622    let bytes = general_purpose::STANDARD.decode(s)?;
1623    Ok(String::from_utf8_lossy(&bytes).to_string())
1624}
1625
1626/// Get a specific statute.
1627async fn get_statute(
1628    user: auth::AuthUser,
1629    State(state): State<Arc<AppState>>,
1630    Path(id): Path<String>,
1631) -> Result<impl IntoResponse, ApiError> {
1632    user.require_permission(auth::Permission::ReadStatutes)?;
1633
1634    let statutes = state.statutes.read().await;
1635    let statute = statutes
1636        .iter()
1637        .find(|s| s.id == id)
1638        .ok_or_else(|| ApiError::NotFound(format!("Statute not found: {}", id)))?;
1639
1640    Ok(Json(ApiResponse::new(statute.clone())))
1641}
1642
1643/// Create a new statute.
1644async fn create_statute(
1645    user: auth::AuthUser,
1646    State(state): State<Arc<AppState>>,
1647    Json(req): Json<CreateStatuteRequest>,
1648) -> Result<impl IntoResponse, ApiError> {
1649    user.require_permission(auth::Permission::CreateStatutes)?;
1650
1651    let mut statutes = state.statutes.write().await;
1652
1653    // Check for duplicate ID
1654    if statutes.iter().any(|s| s.id == req.statute.id) {
1655        return Err(ApiError::BadRequest(format!(
1656            "Statute with ID '{}' already exists",
1657            req.statute.id
1658        )));
1659    }
1660
1661    info!(
1662        "Creating statute: {} by user {}",
1663        req.statute.id, user.username
1664    );
1665
1666    let statute_id = req.statute.id.clone();
1667    let statute_title = req.statute.title.clone();
1668    statutes.push(req.statute.clone());
1669
1670    // Update metrics
1671    metrics::STATUTE_OPERATIONS
1672        .with_label_values(&["create"])
1673        .inc();
1674    metrics::STATUTES_TOTAL.inc();
1675
1676    // Audit log
1677    state
1678        .audit_log
1679        .log_success(
1680            audit::AuditEventType::StatuteCreated,
1681            user.id.to_string(),
1682            user.username.clone(),
1683            "create_statute".to_string(),
1684            Some(statute_id.clone()),
1685            Some("statute".to_string()),
1686            serde_json::json!({
1687                "statute_id": statute_id,
1688                "title": statute_title
1689            }),
1690        )
1691        .await;
1692
1693    // Broadcast WebSocket notification
1694    state
1695        .ws_broadcaster
1696        .broadcast(websocket::WsNotification::StatuteCreated {
1697            statute_id: statute_id.clone(),
1698            title: statute_title,
1699            created_by: user.username.clone(),
1700        });
1701
1702    Ok((StatusCode::CREATED, Json(ApiResponse::new(req.statute))))
1703}
1704
1705/// Compare multiple statutes in a matrix format.
1706async fn compare_statutes_matrix(
1707    user: auth::AuthUser,
1708    State(state): State<Arc<AppState>>,
1709    Json(req): Json<StatuteComparisonMatrixRequest>,
1710) -> Result<impl IntoResponse, ApiError> {
1711    user.require_permission(auth::Permission::ReadStatutes)?;
1712
1713    if req.statute_ids.len() < 2 {
1714        return Err(ApiError::BadRequest(
1715            "At least 2 statutes required for comparison matrix".to_string(),
1716        ));
1717    }
1718
1719    if req.statute_ids.len() > 20 {
1720        return Err(ApiError::BadRequest(
1721            "Maximum 20 statutes allowed for comparison matrix".to_string(),
1722        ));
1723    }
1724
1725    let statutes = state.statutes.read().await;
1726
1727    // Fetch all requested statutes
1728    let mut statute_list = Vec::new();
1729    for id in &req.statute_ids {
1730        if let Some(statute) = statutes.iter().find(|s| &s.id == id) {
1731            statute_list.push(statute.clone());
1732        } else {
1733            return Err(ApiError::NotFound(format!("Statute not found: {}", id)));
1734        }
1735    }
1736
1737    let count = statute_list.len();
1738
1739    // Build similarity matrix (symmetric matrix)
1740    let mut similarity_matrix = vec![vec![0.0; count]; count];
1741    let mut comparisons = Vec::new();
1742
1743    for i in 0..count {
1744        for j in i..count {
1745            if i == j {
1746                // Same statute: 100% similarity
1747                similarity_matrix[i][j] = 100.0;
1748            } else {
1749                // Calculate similarity between statute i and j
1750                let stat_a = &statute_list[i];
1751                let stat_b = &statute_list[j];
1752
1753                let precond_count_a = stat_a.preconditions.len() as i32;
1754                let precond_count_b = stat_b.preconditions.len() as i32;
1755                let precondition_diff = precond_count_b - precond_count_a;
1756
1757                let depth_a = calculate_nesting_depth(&stat_a.preconditions) as i32;
1758                let depth_b = calculate_nesting_depth(&stat_b.preconditions) as i32;
1759                let depth_diff = depth_b - depth_a;
1760
1761                let discretion_a = stat_a.discretion_logic.is_some();
1762                let discretion_b = stat_b.discretion_logic.is_some();
1763                let discretion_differs = discretion_a != discretion_b;
1764
1765                // Calculate similarity score
1766                let mut similarity = 100.0;
1767                similarity -= (precondition_diff.abs() as f64) * 5.0;
1768                similarity -= (depth_diff.abs() as f64) * 10.0;
1769                if discretion_differs {
1770                    similarity -= 20.0;
1771                }
1772                similarity = similarity.clamp(0.0, 100.0);
1773
1774                // Store in matrix (symmetric)
1775                similarity_matrix[i][j] = similarity;
1776                similarity_matrix[j][i] = similarity;
1777
1778                comparisons.push(ComparisonMatrixEntry {
1779                    statute_a_id: stat_a.id.clone(),
1780                    statute_b_id: stat_b.id.clone(),
1781                    similarity_score: similarity,
1782                    precondition_diff,
1783                    discretion_differs,
1784                });
1785            }
1786        }
1787    }
1788
1789    let summaries: Vec<StatuteSummary> = statute_list.iter().map(StatuteSummary::from).collect();
1790
1791    Ok(Json(ApiResponse::new(StatuteComparisonMatrixResponse {
1792        statutes: summaries,
1793        similarity_matrix,
1794        comparisons,
1795    })))
1796}
1797
1798/// Compare two statutes.
1799async fn compare_statutes(
1800    user: auth::AuthUser,
1801    State(state): State<Arc<AppState>>,
1802    Json(req): Json<StatuteComparisonRequest>,
1803) -> Result<impl IntoResponse, ApiError> {
1804    user.require_permission(auth::Permission::ReadStatutes)?;
1805
1806    let statutes = state.statutes.read().await;
1807
1808    let statute_a = statutes
1809        .iter()
1810        .find(|s| s.id == req.statute_id_a)
1811        .ok_or_else(|| ApiError::NotFound(format!("Statute not found: {}", req.statute_id_a)))?;
1812
1813    let statute_b = statutes
1814        .iter()
1815        .find(|s| s.id == req.statute_id_b)
1816        .ok_or_else(|| ApiError::NotFound(format!("Statute not found: {}", req.statute_id_b)))?;
1817
1818    let summary_a = StatuteSummary::from(statute_a);
1819    let summary_b = StatuteSummary::from(statute_b);
1820
1821    let precondition_count_a = statute_a.preconditions.len() as i32;
1822    let precondition_count_b = statute_b.preconditions.len() as i32;
1823
1824    let nesting_depth_a = calculate_nesting_depth(&statute_a.preconditions) as i32;
1825    let nesting_depth_b = calculate_nesting_depth(&statute_b.preconditions) as i32;
1826
1827    let has_discretion_a = statute_a.discretion_logic.is_some();
1828    let has_discretion_b = statute_b.discretion_logic.is_some();
1829
1830    let differences = ComparisonDifferences {
1831        precondition_count_diff: precondition_count_b - precondition_count_a,
1832        nesting_depth_diff: nesting_depth_b - nesting_depth_a,
1833        both_have_discretion: has_discretion_a && has_discretion_b,
1834        discretion_differs: has_discretion_a != has_discretion_b,
1835    };
1836
1837    // Calculate similarity score (0.0 to 100.0)
1838    let mut similarity_score = 100.0;
1839
1840    // Penalize for precondition differences
1841    let precond_diff = (precondition_count_b - precondition_count_a).abs() as f64;
1842    similarity_score -= precond_diff * 5.0;
1843
1844    // Penalize for nesting depth differences
1845    let depth_diff = (nesting_depth_b - nesting_depth_a).abs() as f64;
1846    similarity_score -= depth_diff * 10.0;
1847
1848    // Penalize if discretion differs
1849    if differences.discretion_differs {
1850        similarity_score -= 20.0;
1851    }
1852
1853    similarity_score = similarity_score.clamp(0.0, 100.0);
1854
1855    Ok(Json(ApiResponse::new(StatuteComparisonResponse {
1856        statute_a: summary_a,
1857        statute_b: summary_b,
1858        differences,
1859        similarity_score,
1860    })))
1861}
1862
1863/// Batch create statutes.
1864async fn batch_create_statutes(
1865    user: auth::AuthUser,
1866    State(state): State<Arc<AppState>>,
1867    Json(req): Json<BatchCreateStatutesRequest>,
1868) -> Result<impl IntoResponse, ApiError> {
1869    user.require_permission(auth::Permission::CreateStatutes)?;
1870
1871    if req.statutes.is_empty() {
1872        return Err(ApiError::BadRequest("No statutes provided".to_string()));
1873    }
1874
1875    let mut statutes = state.statutes.write().await;
1876    let mut created = 0;
1877    let mut failed = 0;
1878    let mut errors = Vec::new();
1879    let total_requested = req.statutes.len();
1880
1881    for statute in req.statutes {
1882        // Check for duplicate ID
1883        if statutes.iter().any(|s| s.id == statute.id) {
1884            errors.push(format!("Statute with ID '{}' already exists", statute.id));
1885            failed += 1;
1886            continue;
1887        }
1888
1889        info!(
1890            "Creating statute: {} by user {} (batch)",
1891            statute.id, user.username
1892        );
1893        statutes.push(statute);
1894        created += 1;
1895    }
1896
1897    // Audit log
1898    state
1899        .audit_log
1900        .log_success(
1901            audit::AuditEventType::BatchStatutesCreated,
1902            user.id.to_string(),
1903            user.username.clone(),
1904            "batch_create_statutes".to_string(),
1905            None,
1906            Some("statute".to_string()),
1907            serde_json::json!({
1908                "created": created,
1909                "failed": failed,
1910                "total": total_requested
1911            }),
1912        )
1913        .await;
1914
1915    Ok((
1916        if created > 0 {
1917            StatusCode::CREATED
1918        } else {
1919            StatusCode::BAD_REQUEST
1920        },
1921        Json(ApiResponse::new(BatchCreateStatutesResponse {
1922            created,
1923            failed,
1924            errors,
1925        })),
1926    ))
1927}
1928
1929/// Batch delete statutes.
1930async fn batch_delete_statutes(
1931    user: auth::AuthUser,
1932    State(state): State<Arc<AppState>>,
1933    Json(req): Json<BatchDeleteStatutesRequest>,
1934) -> Result<impl IntoResponse, ApiError> {
1935    user.require_permission(auth::Permission::DeleteStatutes)?;
1936
1937    if req.statute_ids.is_empty() {
1938        return Err(ApiError::BadRequest("No statute IDs provided".to_string()));
1939    }
1940
1941    let mut statutes = state.statutes.write().await;
1942    let mut deleted = 0;
1943    let mut not_found = Vec::new();
1944    let total_requested = req.statute_ids.len();
1945
1946    for id in req.statute_ids {
1947        let initial_len = statutes.len();
1948        statutes.retain(|s| s.id != id);
1949
1950        if statutes.len() < initial_len {
1951            info!("Deleted statute: {} by user {} (batch)", id, user.username);
1952            deleted += 1;
1953        } else {
1954            not_found.push(id);
1955        }
1956    }
1957
1958    // Audit log
1959    state
1960        .audit_log
1961        .log_success(
1962            audit::AuditEventType::BatchStatutesDeleted,
1963            user.id.to_string(),
1964            user.username.clone(),
1965            "batch_delete_statutes".to_string(),
1966            None,
1967            Some("statute".to_string()),
1968            serde_json::json!({
1969                "deleted": deleted,
1970                "not_found": not_found.len(),
1971                "total": total_requested
1972            }),
1973        )
1974        .await;
1975
1976    Ok(Json(ApiResponse::new(BatchDeleteStatutesResponse {
1977        deleted,
1978        not_found,
1979    })))
1980}
1981
1982/// Delete a statute.
1983async fn delete_statute(
1984    user: auth::AuthUser,
1985    State(state): State<Arc<AppState>>,
1986    Path(id): Path<String>,
1987) -> Result<impl IntoResponse, ApiError> {
1988    user.require_permission(auth::Permission::DeleteStatutes)?;
1989
1990    let mut statutes = state.statutes.write().await;
1991    let initial_len = statutes.len();
1992    statutes.retain(|s| s.id != id);
1993
1994    if statutes.len() == initial_len {
1995        return Err(ApiError::NotFound(format!("Statute not found: {}", id)));
1996    }
1997
1998    info!("Deleted statute: {} by user {}", id, user.username);
1999
2000    // Update metrics
2001    metrics::STATUTE_OPERATIONS
2002        .with_label_values(&["delete"])
2003        .inc();
2004    metrics::STATUTES_TOTAL.dec();
2005
2006    // Audit log
2007    state
2008        .audit_log
2009        .log_success(
2010            audit::AuditEventType::StatuteDeleted,
2011            user.id.to_string(),
2012            user.username.clone(),
2013            "delete_statute".to_string(),
2014            Some(id.clone()),
2015            Some("statute".to_string()),
2016            serde_json::json!({
2017                "statute_id": id
2018            }),
2019        )
2020        .await;
2021
2022    // Broadcast WebSocket notification
2023    state
2024        .ws_broadcaster
2025        .broadcast(websocket::WsNotification::StatuteDeleted {
2026            statute_id: id.clone(),
2027            deleted_by: user.username.clone(),
2028        });
2029
2030    Ok(StatusCode::NO_CONTENT)
2031}
2032
2033/// Verify statutes.
2034async fn verify_statutes(
2035    user: auth::AuthUser,
2036    State(state): State<Arc<AppState>>,
2037    Json(req): Json<VerifyRequest>,
2038) -> Result<impl IntoResponse, ApiError> {
2039    user.require_permission(auth::Permission::VerifyStatutes)?;
2040
2041    let statutes = state.statutes.read().await;
2042
2043    let to_verify: Vec<&Statute> = if req.statute_ids.is_empty() {
2044        statutes.iter().collect()
2045    } else {
2046        statutes
2047            .iter()
2048            .filter(|s| req.statute_ids.contains(&s.id))
2049            .collect()
2050    };
2051
2052    if to_verify.is_empty() {
2053        return Err(ApiError::BadRequest("No statutes to verify".to_string()));
2054    }
2055
2056    let verifier = legalis_verifier::StatuteVerifier::new();
2057    let to_verify_owned: Vec<Statute> = to_verify.into_iter().cloned().collect();
2058    let result = verifier.verify(&to_verify_owned);
2059
2060    // Update metrics
2061    metrics::VERIFICATIONS_TOTAL.inc();
2062    metrics::VERIFICATION_RESULTS
2063        .with_label_values(&[if result.passed { "passed" } else { "failed" }])
2064        .inc();
2065
2066    Ok(Json(ApiResponse::new(VerifyResponse {
2067        passed: result.passed,
2068        errors: result.errors.iter().map(|e| e.to_string()).collect(),
2069        warnings: result.warnings.clone(),
2070    })))
2071}
2072
2073/// Verify statutes with detailed report.
2074async fn verify_statutes_detailed(
2075    user: auth::AuthUser,
2076    State(state): State<Arc<AppState>>,
2077    Json(req): Json<VerifyRequest>,
2078) -> Result<impl IntoResponse, ApiError> {
2079    user.require_permission(auth::Permission::VerifyStatutes)?;
2080
2081    let statutes = state.statutes.read().await;
2082
2083    let to_verify: Vec<&Statute> = if req.statute_ids.is_empty() {
2084        statutes.iter().collect()
2085    } else {
2086        statutes
2087            .iter()
2088            .filter(|s| req.statute_ids.contains(&s.id))
2089            .collect()
2090    };
2091
2092    if to_verify.is_empty() {
2093        return Err(ApiError::BadRequest("No statutes to verify".to_string()));
2094    }
2095
2096    let verifier = legalis_verifier::StatuteVerifier::new();
2097    let to_verify_owned: Vec<Statute> = to_verify.into_iter().cloned().collect();
2098    let result = verifier.verify(&to_verify_owned);
2099
2100    let errors: Vec<String> = result.errors.iter().map(|e| e.to_string()).collect();
2101    let warnings = result.warnings.clone();
2102    let suggestions = result.suggestions.clone();
2103
2104    Ok(Json(ApiResponse::new(DetailedVerifyResponse {
2105        passed: result.passed,
2106        total_errors: errors.len(),
2107        total_warnings: warnings.len(),
2108        total_suggestions: suggestions.len(),
2109        errors,
2110        warnings,
2111        suggestions,
2112        statute_count: to_verify_owned.len(),
2113        verified_at: chrono::Utc::now().to_rfc3339(),
2114    })))
2115}
2116
2117/// Detect conflicts between statutes.
2118async fn detect_conflicts(
2119    user: auth::AuthUser,
2120    State(state): State<Arc<AppState>>,
2121    Json(req): Json<ConflictDetectionRequest>,
2122) -> Result<impl IntoResponse, ApiError> {
2123    user.require_permission(auth::Permission::VerifyStatutes)?;
2124
2125    let statutes = state.statutes.read().await;
2126
2127    let to_check: Vec<&Statute> = if req.statute_ids.is_empty() {
2128        statutes.iter().collect()
2129    } else {
2130        statutes
2131            .iter()
2132            .filter(|s| req.statute_ids.contains(&s.id))
2133            .collect()
2134    };
2135
2136    if to_check.len() < 2 {
2137        return Err(ApiError::BadRequest(
2138            "At least 2 statutes required for conflict detection".to_string(),
2139        ));
2140    }
2141
2142    let verifier = legalis_verifier::StatuteVerifier::new();
2143    let to_check_owned: Vec<Statute> = to_check.into_iter().cloned().collect();
2144    let result = verifier.verify(&to_check_owned);
2145
2146    let mut conflicts = Vec::new();
2147
2148    // Extract conflicts from verification errors
2149    for error in result.errors.iter() {
2150        let error_str = error.to_string();
2151        if error_str.contains("conflict") || error_str.contains("contradiction") {
2152            // Parse conflict information from error message
2153            // This is a simplified version; in production, we'd want more structured data
2154            conflicts.push(ConflictInfo {
2155                statute_a_id: "statute-a".to_string(), // Would need to parse from error
2156                statute_b_id: "statute-b".to_string(), // Would need to parse from error
2157                conflict_type: "logical-contradiction".to_string(),
2158                description: error_str,
2159            });
2160        }
2161    }
2162
2163    Ok(Json(ApiResponse::new(ConflictDetectionResponse {
2164        conflict_count: conflicts.len(),
2165        conflicts,
2166    })))
2167}
2168
2169/// Start async verification of statutes.
2170/// Returns a job ID that can be used to poll for results.
2171async fn verify_statutes_async(
2172    user: auth::AuthUser,
2173    State(state): State<Arc<AppState>>,
2174    Json(req): Json<VerifyRequest>,
2175) -> Result<impl IntoResponse, ApiError> {
2176    user.require_permission(auth::Permission::VerifyStatutes)?;
2177
2178    // Create a job
2179    let job_id = state.verification_jobs.create_job().await;
2180
2181    // Clone state for the background task
2182    let state_clone = Arc::clone(&state);
2183    let statute_ids = req.statute_ids.clone();
2184    let job_id_clone = job_id.clone();
2185
2186    // Spawn background task
2187    tokio::spawn(async move {
2188        let job_id = job_id_clone;
2189        // Mark job as running
2190        state_clone
2191            .verification_jobs
2192            .update_job(&job_id, |job| {
2193                job.set_running();
2194            })
2195            .await;
2196
2197        // Get statutes
2198        let statutes = state_clone.statutes.read().await;
2199
2200        let to_verify: Vec<&Statute> = if statute_ids.is_empty() {
2201            statutes.iter().collect()
2202        } else {
2203            statutes
2204                .iter()
2205                .filter(|s| statute_ids.contains(&s.id))
2206                .collect()
2207        };
2208
2209        if to_verify.is_empty() {
2210            state_clone
2211                .verification_jobs
2212                .update_job(&job_id, |job| {
2213                    job.fail("No statutes to verify".to_string());
2214                })
2215                .await;
2216            return;
2217        }
2218
2219        // Update progress
2220        state_clone
2221            .verification_jobs
2222            .update_job(&job_id, |job| {
2223                job.set_progress(30.0);
2224            })
2225            .await;
2226
2227        // Run verification
2228        let verifier = legalis_verifier::StatuteVerifier::new();
2229        let to_verify_owned: Vec<Statute> = to_verify.into_iter().cloned().collect();
2230        let statute_count = to_verify_owned.len();
2231
2232        state_clone
2233            .verification_jobs
2234            .update_job(&job_id, |job| {
2235                job.set_progress(60.0);
2236            })
2237            .await;
2238
2239        let result = verifier.verify(&to_verify_owned);
2240
2241        state_clone
2242            .verification_jobs
2243            .update_job(&job_id, |job| {
2244                job.set_progress(90.0);
2245            })
2246            .await;
2247
2248        // Complete job
2249        let job_result = VerificationJobResult {
2250            passed: result.passed,
2251            errors: result.errors.iter().map(|e| e.to_string()).collect(),
2252            warnings: result.warnings,
2253            statute_count,
2254        };
2255
2256        let passed = job_result.passed;
2257        let errors_count = job_result.errors.len();
2258        let warnings_count = job_result.warnings.len();
2259
2260        state_clone
2261            .verification_jobs
2262            .update_job(&job_id, |job| {
2263                job.complete(job_result);
2264            })
2265            .await;
2266
2267        // Broadcast WebSocket notification
2268        state_clone
2269            .ws_broadcaster
2270            .broadcast(websocket::WsNotification::VerificationCompleted {
2271                job_id: job_id.clone(),
2272                passed,
2273                errors_count,
2274                warnings_count,
2275            });
2276    });
2277
2278    let poll_url = format!("/api/v1/verify/async/{}", job_id);
2279
2280    Ok((
2281        StatusCode::ACCEPTED,
2282        Json(ApiResponse::new(AsyncVerifyStartResponse {
2283            job_id,
2284            status: "pending".to_string(),
2285            poll_url,
2286        })),
2287    ))
2288}
2289
2290/// Get async verification job status.
2291async fn get_verification_job_status(
2292    user: auth::AuthUser,
2293    State(state): State<Arc<AppState>>,
2294    Path(job_id): Path<String>,
2295) -> Result<impl IntoResponse, ApiError> {
2296    user.require_permission(auth::Permission::VerifyStatutes)?;
2297
2298    let job = state
2299        .verification_jobs
2300        .get_job(&job_id)
2301        .await
2302        .ok_or_else(|| ApiError::NotFound(format!("Job not found: {}", job_id)))?;
2303
2304    let status_str = match job.status {
2305        async_jobs::JobStatus::Pending => "pending",
2306        async_jobs::JobStatus::Running => "running",
2307        async_jobs::JobStatus::Completed => "completed",
2308        async_jobs::JobStatus::Failed => "failed",
2309    }
2310    .to_string();
2311
2312    Ok(Json(ApiResponse::new(JobStatusResponse {
2313        id: job.id,
2314        status: status_str,
2315        progress: job.progress,
2316        result: job.result,
2317        error: job.error,
2318        created_at: job.created_at.to_rfc3339(),
2319        updated_at: job.updated_at.to_rfc3339(),
2320    })))
2321}
2322
2323/// Bulk verification with streaming results via Server-Sent Events.
2324/// Verifies statutes in bulk and streams progress updates in real-time.
2325async fn verify_bulk_stream(
2326    user: auth::AuthUser,
2327    State(state): State<Arc<AppState>>,
2328    Json(req): Json<BatchVerifyRequest>,
2329) -> Result<Sse<impl Stream<Item = Result<Event, Infallible>>>, ApiError> {
2330    user.require_permission(auth::Permission::VerifyStatutes)?;
2331
2332    if req.jobs.is_empty() {
2333        return Err(ApiError::BadRequest(
2334            "No verification jobs provided".to_string(),
2335        ));
2336    }
2337
2338    // Clone data for async stream
2339    let statutes = state.statutes.read().await.clone();
2340
2341    // Create stream
2342    let stream = stream::unfold(
2343        (req.jobs, statutes, 0usize),
2344        |(mut jobs, statutes, processed)| async move {
2345            if processed == 0 {
2346                // Send start event
2347                let event = Event::default()
2348                    .event("start")
2349                    .json_data(serde_json::json!({
2350                        "total_jobs": jobs.len(),
2351                        "status": "started"
2352                    }))
2353                    .ok()?;
2354                return Some((Ok::<_, Infallible>(event), (jobs, statutes, processed)));
2355            }
2356
2357            if jobs.is_empty() {
2358                // Send completion event
2359                let event = Event::default()
2360                    .event("complete")
2361                    .json_data(serde_json::json!({
2362                        "status": "completed",
2363                        "total_processed": processed
2364                    }))
2365                    .ok()?;
2366                return Some((Ok::<_, Infallible>(event), (jobs, statutes, processed)));
2367            }
2368
2369            // Process next job
2370            let job = jobs.remove(0);
2371            let verifier = legalis_verifier::StatuteVerifier::new();
2372
2373            let to_verify: Vec<&Statute> = if job.statute_ids.is_empty() {
2374                statutes.iter().collect()
2375            } else {
2376                statutes
2377                    .iter()
2378                    .filter(|s| job.statute_ids.contains(&s.id))
2379                    .collect()
2380            };
2381
2382            let to_verify_owned: Vec<Statute> = to_verify.into_iter().cloned().collect();
2383            let statute_count = to_verify_owned.len();
2384
2385            let result = if statute_count == 0 {
2386                BatchVerifyResult {
2387                    job_id: job.job_id.clone(),
2388                    passed: false,
2389                    errors: vec!["No statutes found for verification".to_string()],
2390                    warnings: vec![],
2391                    statute_count: 0,
2392                }
2393            } else {
2394                let verify_result = verifier.verify(&to_verify_owned);
2395                BatchVerifyResult {
2396                    job_id: job.job_id,
2397                    passed: verify_result.passed,
2398                    errors: verify_result.errors.iter().map(|e| e.to_string()).collect(),
2399                    warnings: verify_result.warnings.clone(),
2400                    statute_count,
2401                }
2402            };
2403
2404            let processed_count = processed + 1;
2405            let event = Event::default()
2406                .event("result")
2407                .json_data(serde_json::json!({
2408                    "job_index": processed_count,
2409                    "total_jobs": processed_count + jobs.len(),
2410                    "result": result,
2411                    "progress": (processed_count as f64 / (processed_count + jobs.len()) as f64) * 100.0
2412                }))
2413                .ok()?;
2414
2415            Some((
2416                Ok::<_, Infallible>(event),
2417                (jobs, statutes, processed_count),
2418            ))
2419        },
2420    );
2421
2422    Ok(Sse::new(stream).keep_alive(KeepAlive::default()))
2423}
2424
2425/// Batch verification of multiple statute groups.
2426/// Each job is verified independently, allowing parallel processing.
2427async fn verify_batch(
2428    user: auth::AuthUser,
2429    State(state): State<Arc<AppState>>,
2430    Json(req): Json<BatchVerifyRequest>,
2431) -> Result<impl IntoResponse, ApiError> {
2432    user.require_permission(auth::Permission::VerifyStatutes)?;
2433
2434    if req.jobs.is_empty() {
2435        return Err(ApiError::BadRequest(
2436            "No verification jobs provided".to_string(),
2437        ));
2438    }
2439
2440    let statutes = state.statutes.read().await;
2441    let verifier = legalis_verifier::StatuteVerifier::new();
2442
2443    // Process each job
2444    let mut results = Vec::new();
2445    let total_jobs = req.jobs.len();
2446    for job in req.jobs {
2447        let to_verify: Vec<&Statute> = if job.statute_ids.is_empty() {
2448            statutes.iter().collect()
2449        } else {
2450            statutes
2451                .iter()
2452                .filter(|s| job.statute_ids.contains(&s.id))
2453                .collect()
2454        };
2455
2456        let to_verify_owned: Vec<Statute> = to_verify.into_iter().cloned().collect();
2457        let statute_count = to_verify_owned.len();
2458
2459        // Skip empty jobs
2460        if statute_count == 0 {
2461            results.push(BatchVerifyResult {
2462                job_id: job.job_id.clone(),
2463                passed: false,
2464                errors: vec!["No statutes found for verification".to_string()],
2465                warnings: vec![],
2466                statute_count: 0,
2467            });
2468            continue;
2469        }
2470
2471        let result = verifier.verify(&to_verify_owned);
2472
2473        results.push(BatchVerifyResult {
2474            job_id: job.job_id,
2475            passed: result.passed,
2476            errors: result.errors.iter().map(|e| e.to_string()).collect(),
2477            warnings: result.warnings.clone(),
2478            statute_count,
2479        });
2480    }
2481
2482    let passed_jobs = results.iter().filter(|r| r.passed).count();
2483    let failed_jobs = results.len() - passed_jobs;
2484
2485    Ok(Json(ApiResponse::new(BatchVerifyResponse {
2486        results,
2487        total_jobs,
2488        passed_jobs,
2489        failed_jobs,
2490    })))
2491}
2492
2493/// Analyze complexity of a statute.
2494async fn analyze_complexity(
2495    user: auth::AuthUser,
2496    State(state): State<Arc<AppState>>,
2497    Path(id): Path<String>,
2498) -> Result<impl IntoResponse, ApiError> {
2499    user.require_permission(auth::Permission::ReadStatutes)?;
2500
2501    let statutes = state.statutes.read().await;
2502    let statute = statutes
2503        .iter()
2504        .find(|s| s.id == id)
2505        .ok_or_else(|| ApiError::NotFound(format!("Statute not found: {}", id)))?;
2506
2507    // Calculate complexity metrics
2508    let precondition_count = statute.preconditions.len();
2509    let nesting_depth = calculate_nesting_depth(&statute.preconditions);
2510    let has_discretion = statute.discretion_logic.is_some();
2511
2512    // Simple complexity score formula
2513    let complexity_score = (precondition_count as f64 * 1.5)
2514        + (nesting_depth as f64 * 2.0)
2515        + if has_discretion { 5.0 } else { 0.0 };
2516
2517    Ok(Json(ApiResponse::new(ComplexityResponse {
2518        statute_id: id,
2519        complexity_score,
2520        precondition_count,
2521        nesting_depth,
2522        has_discretion,
2523    })))
2524}
2525
2526/// Get all versions of a statute by base ID.
2527/// Statutes are grouped by their base ID (the part before the version suffix).
2528async fn get_statute_versions(
2529    user: auth::AuthUser,
2530    State(state): State<Arc<AppState>>,
2531    Path(base_id): Path<String>,
2532) -> Result<impl IntoResponse, ApiError> {
2533    user.require_permission(auth::Permission::ReadStatutes)?;
2534
2535    let statutes = state.statutes.read().await;
2536
2537    // Find all statutes that match the base_id
2538    // We consider statutes with the same ID or ID that starts with base_id
2539    let versions: Vec<StatuteVersionInfo> = statutes
2540        .iter()
2541        .filter(|s| s.id == base_id || s.id.starts_with(&format!("{}-v", base_id)))
2542        .map(|s| StatuteVersionInfo {
2543            id: s.id.clone(),
2544            version: s.version,
2545            title: s.title.clone(),
2546            created_at: None, // Would need to track creation timestamps
2547        })
2548        .collect();
2549
2550    if versions.is_empty() {
2551        return Err(ApiError::NotFound(format!(
2552            "No statutes found with base ID: {}",
2553            base_id
2554        )));
2555    }
2556
2557    let total_versions = versions.len();
2558
2559    Ok(Json(ApiResponse::new(StatuteVersionListResponse {
2560        base_id,
2561        versions,
2562        total_versions,
2563    })))
2564}
2565
2566/// Create a new version of an existing statute.
2567async fn create_statute_version(
2568    user: auth::AuthUser,
2569    State(state): State<Arc<AppState>>,
2570    Path(id): Path<String>,
2571    Json(req): Json<CreateVersionRequest>,
2572) -> Result<impl IntoResponse, ApiError> {
2573    user.require_permission(auth::Permission::CreateStatutes)?;
2574
2575    let mut statutes = state.statutes.write().await;
2576
2577    // Find the original statute
2578    let original = statutes
2579        .iter()
2580        .find(|s| s.id == id)
2581        .ok_or_else(|| ApiError::NotFound(format!("Statute not found: {}", id)))?
2582        .clone();
2583
2584    // Find the highest version number for this base statute
2585    let base_id = if original.id.contains("-v") {
2586        original.id.split("-v").next().unwrap_or(&original.id)
2587    } else {
2588        &original.id
2589    };
2590
2591    let max_version = statutes
2592        .iter()
2593        .filter(|s| s.id == base_id || s.id.starts_with(&format!("{}-v", base_id)))
2594        .map(|s| s.version)
2595        .max()
2596        .unwrap_or(original.version);
2597
2598    let new_version = max_version + 1;
2599    let new_id = format!("{}-v{}", base_id, new_version);
2600
2601    // Check if new ID already exists
2602    if statutes.iter().any(|s| s.id == new_id) {
2603        return Err(ApiError::BadRequest(format!(
2604            "Statute version already exists: {}",
2605            new_id
2606        )));
2607    }
2608
2609    // Create new version based on original with optional modifications
2610    let mut new_statute = original.clone();
2611    new_statute.id = new_id.clone();
2612    new_statute.version = new_version;
2613
2614    if let Some(title) = req.title {
2615        new_statute.title = title;
2616    }
2617
2618    if let Some(preconditions) = req.preconditions {
2619        new_statute.preconditions = preconditions;
2620    }
2621
2622    if let Some(effect) = req.effect {
2623        new_statute.effect = effect;
2624    }
2625
2626    if let Some(discretion) = req.discretion_logic {
2627        new_statute.discretion_logic = Some(discretion);
2628    }
2629
2630    info!(
2631        "Creating statute version: {} (v{}) by user {}",
2632        new_id, new_version, user.username
2633    );
2634    statutes.push(new_statute.clone());
2635
2636    // Audit log
2637    state
2638        .audit_log
2639        .log_success(
2640            audit::AuditEventType::StatuteVersionCreated,
2641            user.id.to_string(),
2642            user.username.clone(),
2643            "create_statute_version".to_string(),
2644            Some(new_id.clone()),
2645            Some("statute".to_string()),
2646            serde_json::json!({
2647                "statute_id": new_id,
2648                "version": new_version,
2649                "base_id": base_id
2650            }),
2651        )
2652        .await;
2653
2654    Ok((StatusCode::CREATED, Json(ApiResponse::new(new_statute))))
2655}
2656
2657/// Helper function to calculate nesting depth of conditions.
2658fn calculate_nesting_depth(conditions: &[legalis_core::Condition]) -> usize {
2659    use legalis_core::Condition;
2660
2661    fn depth_of_condition(cond: &Condition) -> usize {
2662        match cond {
2663            Condition::And(left, right) | Condition::Or(left, right) => {
2664                1 + depth_of_condition(left).max(depth_of_condition(right))
2665            }
2666            Condition::Not(inner) => 1 + depth_of_condition(inner),
2667            _ => 0,
2668        }
2669    }
2670
2671    conditions.iter().map(depth_of_condition).max().unwrap_or(0)
2672}
2673
2674/// Run a simulation on statutes with a generated population.
2675async fn run_simulation(
2676    user: auth::AuthUser,
2677    State(state): State<Arc<AppState>>,
2678    Json(req): Json<SimulationRequest>,
2679) -> Result<impl IntoResponse, ApiError> {
2680    user.require_permission(auth::Permission::VerifyStatutes)?;
2681
2682    if req.population_size == 0 {
2683        return Err(ApiError::BadRequest(
2684            "Population size must be greater than 0".to_string(),
2685        ));
2686    }
2687
2688    if req.population_size > 10000 {
2689        return Err(ApiError::BadRequest(
2690            "Population size cannot exceed 10000".to_string(),
2691        ));
2692    }
2693
2694    let statutes = state.statutes.read().await;
2695
2696    let to_simulate: Vec<Statute> = if req.statute_ids.is_empty() {
2697        statutes.clone()
2698    } else {
2699        statutes
2700            .iter()
2701            .filter(|s| req.statute_ids.contains(&s.id))
2702            .cloned()
2703            .collect()
2704    };
2705
2706    if to_simulate.is_empty() {
2707        return Err(ApiError::BadRequest("No statutes to simulate".to_string()));
2708    }
2709
2710    // Create population
2711    use legalis_core::{LegalEntity, TypedEntity};
2712    let mut population: Vec<Box<dyn LegalEntity>> = Vec::new();
2713    for i in 0..req.population_size {
2714        let mut entity = TypedEntity::new();
2715
2716        // Set age with some variation
2717        entity.set_u32("age", 18 + (i % 50) as u32);
2718
2719        // Set income with variation
2720        entity.set_u64("income", 20000 + ((i * 1000) % 80000) as u64);
2721
2722        // Apply custom entity parameters from request
2723        for (key, value) in &req.entity_params {
2724            entity.set_string(key, value);
2725        }
2726
2727        population.push(Box::new(entity));
2728    }
2729
2730    // Run simulation
2731    use legalis_sim::SimEngine;
2732    let engine = SimEngine::new(to_simulate.clone(), population);
2733    let sim_metrics = engine.run_simulation().await;
2734
2735    // Update business metrics
2736    metrics::SIMULATIONS_TOTAL.inc();
2737    metrics::SIMULATION_OUTCOMES
2738        .with_label_values(&["deterministic"])
2739        .inc_by(sim_metrics.deterministic_count as u64);
2740    metrics::SIMULATION_OUTCOMES
2741        .with_label_values(&["discretionary"])
2742        .inc_by(sim_metrics.discretion_count as u64);
2743    metrics::SIMULATION_OUTCOMES
2744        .with_label_values(&["void"])
2745        .inc_by(sim_metrics.void_count as u64);
2746
2747    let total = sim_metrics.total_applications as f64;
2748    let deterministic_rate = if total > 0.0 {
2749        (sim_metrics.deterministic_count as f64 / total) * 100.0
2750    } else {
2751        0.0
2752    };
2753    let discretionary_rate = if total > 0.0 {
2754        (sim_metrics.discretion_count as f64 / total) * 100.0
2755    } else {
2756        0.0
2757    };
2758    let void_rate = if total > 0.0 {
2759        (sim_metrics.void_count as f64 / total) * 100.0
2760    } else {
2761        0.0
2762    };
2763
2764    Ok(Json(ApiResponse::new(SimulationResponse {
2765        simulation_id: uuid::Uuid::new_v4().to_string(),
2766        total_entities: req.population_size,
2767        deterministic_outcomes: sim_metrics.deterministic_count,
2768        discretionary_outcomes: sim_metrics.discretion_count,
2769        void_outcomes: sim_metrics.void_count,
2770        deterministic_rate,
2771        discretionary_rate,
2772        void_rate,
2773        completed_at: chrono::Utc::now().to_rfc3339(),
2774    })))
2775}
2776
2777/// Stream simulation results in real-time using Server-Sent Events.
2778async fn stream_simulation(
2779    user: auth::AuthUser,
2780    State(state): State<Arc<AppState>>,
2781    Json(req): Json<SimulationRequest>,
2782) -> Result<Sse<impl Stream<Item = Result<Event, Infallible>>>, ApiError> {
2783    user.require_permission(auth::Permission::VerifyStatutes)?;
2784
2785    if req.population_size == 0 {
2786        return Err(ApiError::BadRequest(
2787            "Population size must be greater than 0".to_string(),
2788        ));
2789    }
2790
2791    if req.population_size > 10000 {
2792        return Err(ApiError::BadRequest(
2793            "Population size cannot exceed 10000".to_string(),
2794        ));
2795    }
2796
2797    let statutes = state.statutes.read().await;
2798
2799    let to_simulate: Vec<Statute> = if req.statute_ids.is_empty() {
2800        statutes.clone()
2801    } else {
2802        statutes
2803            .iter()
2804            .filter(|s| req.statute_ids.contains(&s.id))
2805            .cloned()
2806            .collect()
2807    };
2808
2809    if to_simulate.is_empty() {
2810        return Err(ApiError::BadRequest("No statutes to simulate".to_string()));
2811    }
2812
2813    drop(statutes); // Release the read lock
2814
2815    // Create population
2816    use legalis_core::{LegalEntity, TypedEntity};
2817    let mut population: Vec<Box<dyn LegalEntity>> = Vec::new();
2818    for i in 0..req.population_size {
2819        let mut entity = TypedEntity::new();
2820        entity.set_u32("age", 18 + (i % 50) as u32);
2821        entity.set_u64("income", 20000 + ((i * 1000) % 80000) as u64);
2822
2823        for (key, value) in &req.entity_params {
2824            entity.set_string(key, value);
2825        }
2826
2827        population.push(Box::new(entity));
2828    }
2829
2830    let simulation_id = uuid::Uuid::new_v4().to_string();
2831    let total_entities = req.population_size;
2832
2833    // Create an async stream
2834    let stream = stream::unfold(
2835        (
2836            to_simulate,
2837            population,
2838            0usize,
2839            simulation_id.clone(),
2840            total_entities,
2841        ),
2842        |(statutes, population, progress, sim_id, total_entities)| async move {
2843            if progress == 0 {
2844                // Send start event
2845                let event = Event::default()
2846                    .event("start")
2847                    .json_data(serde_json::json!({
2848                        "simulation_id": sim_id,
2849                        "total_entities": population.len(),
2850                        "status": "started"
2851                    }))
2852                    .ok()?;
2853                return Some((
2854                    Ok::<_, Infallible>(event),
2855                    (statutes, population, 10, sim_id, total_entities),
2856                ));
2857            }
2858
2859            if progress < 100 {
2860                // Send progress update
2861                tokio::time::sleep(Duration::from_millis(100)).await;
2862                let event = Event::default()
2863                    .event("progress")
2864                    .json_data(serde_json::json!({
2865                        "simulation_id": sim_id,
2866                        "progress": progress,
2867                        "status": "running"
2868                    }))
2869                    .ok()?;
2870                return Some((
2871                    Ok::<_, Infallible>(event),
2872                    (statutes, population, progress + 10, sim_id, total_entities),
2873                ));
2874            }
2875
2876            if progress == 100 {
2877                // Run actual simulation
2878                use legalis_sim::SimEngine;
2879                let engine = SimEngine::new(statutes.clone(), population);
2880                let metrics = engine.run_simulation().await;
2881
2882                let total = metrics.total_applications as f64;
2883                let deterministic_rate = if total > 0.0 {
2884                    (metrics.deterministic_count as f64 / total) * 100.0
2885                } else {
2886                    0.0
2887                };
2888                let discretionary_rate = if total > 0.0 {
2889                    (metrics.discretion_count as f64 / total) * 100.0
2890                } else {
2891                    0.0
2892                };
2893                let void_rate = if total > 0.0 {
2894                    (metrics.void_count as f64 / total) * 100.0
2895                } else {
2896                    0.0
2897                };
2898
2899                // Send completion event
2900                let event = Event::default()
2901                    .event("complete")
2902                    .json_data(serde_json::json!({
2903                        "simulation_id": sim_id,
2904                        "status": "completed",
2905                        "total_entities": total_entities,
2906                        "deterministic_outcomes": metrics.deterministic_count,
2907                        "discretionary_outcomes": metrics.discretion_count,
2908                        "void_outcomes": metrics.void_count,
2909                        "deterministic_rate": deterministic_rate,
2910                        "discretionary_rate": discretionary_rate,
2911                        "void_rate": void_rate,
2912                        "completed_at": chrono::Utc::now().to_rfc3339()
2913                    }))
2914                    .ok()?;
2915                return Some((
2916                    Ok::<_, Infallible>(event),
2917                    (statutes, vec![], 101, sim_id, total_entities),
2918                ));
2919            }
2920
2921            None
2922        },
2923    );
2924
2925    Ok(Sse::new(stream).keep_alive(KeepAlive::default()))
2926}
2927
2928/// Compare two simulation scenarios.
2929async fn compare_simulations(
2930    user: auth::AuthUser,
2931    State(state): State<Arc<AppState>>,
2932    Json(req): Json<SimulationComparisonRequest>,
2933) -> Result<impl IntoResponse, ApiError> {
2934    user.require_permission(auth::Permission::VerifyStatutes)?;
2935
2936    if req.population_size == 0 || req.population_size > 10000 {
2937        return Err(ApiError::BadRequest(
2938            "Population size must be between 1 and 10000".to_string(),
2939        ));
2940    }
2941
2942    let statutes = state.statutes.read().await;
2943
2944    let statutes_a: Vec<Statute> = statutes
2945        .iter()
2946        .filter(|s| req.statute_ids_a.contains(&s.id))
2947        .cloned()
2948        .collect();
2949
2950    let statutes_b: Vec<Statute> = statutes
2951        .iter()
2952        .filter(|s| req.statute_ids_b.contains(&s.id))
2953        .cloned()
2954        .collect();
2955
2956    if statutes_a.is_empty() || statutes_b.is_empty() {
2957        return Err(ApiError::BadRequest(
2958            "Both scenarios must have at least one statute".to_string(),
2959        ));
2960    }
2961
2962    // Helper function to create population
2963    fn create_population(size: usize) -> Vec<Box<dyn legalis_core::LegalEntity>> {
2964        use legalis_core::TypedEntity;
2965        let mut population: Vec<Box<dyn legalis_core::LegalEntity>> = Vec::new();
2966        for i in 0..size {
2967            let mut entity = TypedEntity::new();
2968            entity.set_u32("age", 18 + (i % 50) as u32);
2969            entity.set_u64("income", 20000 + ((i * 1000) % 80000) as u64);
2970            population.push(Box::new(entity));
2971        }
2972        population
2973    }
2974
2975    // Run scenario A
2976    use legalis_sim::SimEngine;
2977    let population_a = create_population(req.population_size);
2978    let engine_a = SimEngine::new(statutes_a, population_a);
2979    let metrics_a = engine_a.run_simulation().await;
2980
2981    // Run scenario B
2982    let population_b = create_population(req.population_size);
2983    let engine_b = SimEngine::new(statutes_b, population_b);
2984    let metrics_b = engine_b.run_simulation().await;
2985
2986    let total = req.population_size as f64;
2987
2988    let det_rate_a = (metrics_a.deterministic_count as f64 / total) * 100.0;
2989    let disc_rate_a = (metrics_a.discretion_count as f64 / total) * 100.0;
2990    let void_rate_a = (metrics_a.void_count as f64 / total) * 100.0;
2991
2992    let det_rate_b = (metrics_b.deterministic_count as f64 / total) * 100.0;
2993    let disc_rate_b = (metrics_b.discretion_count as f64 / total) * 100.0;
2994    let void_rate_b = (metrics_b.void_count as f64 / total) * 100.0;
2995
2996    let det_diff = det_rate_b - det_rate_a;
2997    let disc_diff = disc_rate_b - disc_rate_a;
2998    let void_diff = void_rate_b - void_rate_a;
2999
3000    // Consider change significant if any rate changes by more than 10%
3001    let significant_change =
3002        det_diff.abs() > 10.0 || disc_diff.abs() > 10.0 || void_diff.abs() > 10.0;
3003
3004    Ok(Json(ApiResponse::new(SimulationComparisonResponse {
3005        scenario_a: SimulationScenarioResult {
3006            name: "Scenario A".to_string(),
3007            deterministic_rate: det_rate_a,
3008            discretionary_rate: disc_rate_a,
3009            void_rate: void_rate_a,
3010        },
3011        scenario_b: SimulationScenarioResult {
3012            name: "Scenario B".to_string(),
3013            deterministic_rate: det_rate_b,
3014            discretionary_rate: disc_rate_b,
3015            void_rate: void_rate_b,
3016        },
3017        differences: SimulationDifferences {
3018            deterministic_diff: det_diff,
3019            discretionary_diff: disc_diff,
3020            void_diff,
3021            significant_change,
3022        },
3023    })))
3024}
3025
3026/// Check compliance of a specific entity against statutes.
3027async fn check_compliance(
3028    user: auth::AuthUser,
3029    State(state): State<Arc<AppState>>,
3030    Json(req): Json<ComplianceCheckRequest>,
3031) -> Result<impl IntoResponse, ApiError> {
3032    user.require_permission(auth::Permission::VerifyStatutes)?;
3033
3034    let statutes = state.statutes.read().await;
3035
3036    let to_check: Vec<Statute> = if req.statute_ids.is_empty() {
3037        statutes.clone()
3038    } else {
3039        statutes
3040            .iter()
3041            .filter(|s| req.statute_ids.contains(&s.id))
3042            .cloned()
3043            .collect()
3044    };
3045
3046    if to_check.is_empty() {
3047        return Err(ApiError::BadRequest("No statutes to check".to_string()));
3048    }
3049
3050    drop(statutes);
3051
3052    // Create entity from provided attributes
3053    use legalis_core::TypedEntity;
3054    let mut entity = TypedEntity::new();
3055    for (key, value) in &req.entity_attributes {
3056        // Try to parse as different types
3057        if let Ok(num) = value.parse::<u32>() {
3058            entity.set_u32(key, num);
3059        } else if let Ok(num) = value.parse::<u64>() {
3060            entity.set_u64(key, num);
3061        } else {
3062            entity.set_string(key, value);
3063        }
3064    }
3065
3066    // Check compliance by simulating with single entity
3067    use legalis_sim::SimEngine;
3068    let population: Vec<Box<dyn legalis_core::LegalEntity>> = vec![Box::new(entity)];
3069    let engine = SimEngine::new(to_check.clone(), population);
3070    let metrics = engine.run_simulation().await;
3071
3072    let compliant = metrics.deterministic_count > 0;
3073    let requires_discretion = metrics.discretion_count > 0;
3074    let not_applicable = metrics.void_count > 0;
3075
3076    // Determine which statutes apply
3077    let applicable_statutes: Vec<String> = to_check.iter().map(|s| s.id.clone()).collect();
3078
3079    Ok(Json(ApiResponse::new(ComplianceCheckResponse {
3080        compliant,
3081        requires_discretion,
3082        not_applicable,
3083        applicable_statutes,
3084        checked_statute_count: to_check.len(),
3085    })))
3086}
3087
3088/// Perform what-if analysis by comparing entity with modified attributes.
3089async fn whatif_analysis(
3090    user: auth::AuthUser,
3091    State(state): State<Arc<AppState>>,
3092    Json(req): Json<WhatIfRequest>,
3093) -> Result<impl IntoResponse, ApiError> {
3094    user.require_permission(auth::Permission::VerifyStatutes)?;
3095
3096    let statutes = state.statutes.read().await;
3097
3098    let to_analyze: Vec<Statute> = if req.statute_ids.is_empty() {
3099        statutes.clone()
3100    } else {
3101        statutes
3102            .iter()
3103            .filter(|s| req.statute_ids.contains(&s.id))
3104            .cloned()
3105            .collect()
3106    };
3107
3108    if to_analyze.is_empty() {
3109        return Err(ApiError::BadRequest(
3110            "No statutes for what-if analysis".to_string(),
3111        ));
3112    }
3113
3114    drop(statutes);
3115
3116    // Helper to create entity from attributes
3117    fn create_entity(
3118        attributes: &std::collections::HashMap<String, String>,
3119    ) -> legalis_core::TypedEntity {
3120        use legalis_core::TypedEntity;
3121        let mut entity = TypedEntity::new();
3122        for (key, value) in attributes {
3123            if let Ok(num) = value.parse::<u32>() {
3124                entity.set_u32(key, num);
3125            } else if let Ok(num) = value.parse::<u64>() {
3126                entity.set_u64(key, num);
3127            } else {
3128                entity.set_string(key, value);
3129            }
3130        }
3131        entity
3132    }
3133
3134    // Baseline scenario
3135    let baseline_entity = create_entity(&req.baseline_attributes);
3136    let baseline_pop: Vec<Box<dyn legalis_core::LegalEntity>> = vec![Box::new(baseline_entity)];
3137
3138    use legalis_sim::SimEngine;
3139    let baseline_engine = SimEngine::new(to_analyze.clone(), baseline_pop);
3140    let baseline_metrics = baseline_engine.run_simulation().await;
3141
3142    // Modified scenario
3143    let modified_entity = create_entity(&req.modified_attributes);
3144    let modified_pop: Vec<Box<dyn legalis_core::LegalEntity>> = vec![Box::new(modified_entity)];
3145
3146    let modified_engine = SimEngine::new(to_analyze.clone(), modified_pop);
3147    let modified_metrics = modified_engine.run_simulation().await;
3148
3149    let baseline_compliant = baseline_metrics.deterministic_count > 0;
3150    let modified_compliant = modified_metrics.deterministic_count > 0;
3151
3152    let impact = if baseline_compliant && !modified_compliant {
3153        "negative".to_string()
3154    } else if !baseline_compliant && modified_compliant {
3155        "positive".to_string()
3156    } else {
3157        "none".to_string()
3158    };
3159
3160    Ok(Json(ApiResponse::new(WhatIfResponse {
3161        baseline_compliant,
3162        modified_compliant,
3163        impact,
3164        baseline_requires_discretion: baseline_metrics.discretion_count > 0,
3165        modified_requires_discretion: modified_metrics.discretion_count > 0,
3166        changed_attribute_count: req.modified_attributes.len(),
3167    })))
3168}
3169
3170/// Save a simulation result for later retrieval.
3171async fn save_simulation(
3172    user: auth::AuthUser,
3173    State(state): State<Arc<AppState>>,
3174    Json(req): Json<SaveSimulationRequest>,
3175) -> Result<impl IntoResponse, ApiError> {
3176    user.require_permission(auth::Permission::CreateStatutes)?;
3177
3178    let saved = SavedSimulation {
3179        id: uuid::Uuid::new_v4().to_string(),
3180        name: req.name,
3181        description: req.description,
3182        statute_ids: vec![], // Would need to track in SimulationResponse
3183        population_size: req.simulation_result.total_entities,
3184        deterministic_outcomes: req.simulation_result.deterministic_outcomes,
3185        discretionary_outcomes: req.simulation_result.discretionary_outcomes,
3186        void_outcomes: req.simulation_result.void_outcomes,
3187        deterministic_rate: req.simulation_result.deterministic_rate,
3188        discretionary_rate: req.simulation_result.discretionary_rate,
3189        void_rate: req.simulation_result.void_rate,
3190        created_at: chrono::Utc::now().to_rfc3339(),
3191        created_by: user.username.clone(),
3192    };
3193
3194    let mut simulations = state.saved_simulations.write().await;
3195    simulations.push(saved.clone());
3196
3197    info!("Saved simulation: {} by user {}", saved.id, user.username);
3198
3199    // Audit log
3200    state
3201        .audit_log
3202        .log_success(
3203            audit::AuditEventType::SimulationSaved,
3204            user.id.to_string(),
3205            user.username.clone(),
3206            "save_simulation".to_string(),
3207            Some(saved.id.clone()),
3208            Some("simulation".to_string()),
3209            serde_json::json!({
3210                "simulation_id": saved.id,
3211                "name": saved.name
3212            }),
3213        )
3214        .await;
3215
3216    Ok((StatusCode::CREATED, Json(ApiResponse::new(saved))))
3217}
3218
3219/// List all saved simulations.
3220async fn list_saved_simulations(
3221    user: auth::AuthUser,
3222    State(state): State<Arc<AppState>>,
3223    Query(query): Query<ListSavedSimulationsQuery>,
3224) -> Result<impl IntoResponse, ApiError> {
3225    user.require_permission(auth::Permission::ReadStatutes)?;
3226
3227    let simulations = state.saved_simulations.read().await;
3228    let total = simulations.len();
3229
3230    let offset = query.offset.unwrap_or(0);
3231    let limit = query.limit.unwrap_or(100).min(1000);
3232
3233    let paginated: Vec<SavedSimulation> = simulations
3234        .iter()
3235        .skip(offset)
3236        .take(limit)
3237        .cloned()
3238        .collect();
3239
3240    let meta = ResponseMeta {
3241        total: Some(total),
3242        page: Some(offset / limit),
3243        per_page: Some(limit),
3244        next_cursor: None,
3245        prev_cursor: None,
3246        has_more: None,
3247    };
3248
3249    Ok(Json(ApiResponse::new(paginated).with_meta(meta)))
3250}
3251
3252/// Get a specific saved simulation.
3253async fn get_saved_simulation(
3254    user: auth::AuthUser,
3255    State(state): State<Arc<AppState>>,
3256    Path(id): Path<String>,
3257) -> Result<impl IntoResponse, ApiError> {
3258    user.require_permission(auth::Permission::ReadStatutes)?;
3259
3260    let simulations = state.saved_simulations.read().await;
3261    let simulation = simulations
3262        .iter()
3263        .find(|s| s.id == id)
3264        .ok_or_else(|| ApiError::NotFound(format!("Saved simulation not found: {}", id)))?;
3265
3266    Ok(Json(ApiResponse::new(simulation.clone())))
3267}
3268
3269/// Delete a saved simulation.
3270async fn delete_saved_simulation(
3271    user: auth::AuthUser,
3272    State(state): State<Arc<AppState>>,
3273    Path(id): Path<String>,
3274) -> Result<impl IntoResponse, ApiError> {
3275    user.require_permission(auth::Permission::DeleteStatutes)?;
3276
3277    let mut simulations = state.saved_simulations.write().await;
3278    let initial_len = simulations.len();
3279    simulations.retain(|s| s.id != id);
3280
3281    if simulations.len() == initial_len {
3282        return Err(ApiError::NotFound(format!(
3283            "Saved simulation not found: {}",
3284            id
3285        )));
3286    }
3287
3288    info!("Deleted saved simulation: {} by user {}", id, user.username);
3289
3290    // Audit log
3291    state
3292        .audit_log
3293        .log_success(
3294            audit::AuditEventType::SimulationDeleted,
3295            user.id.to_string(),
3296            user.username.clone(),
3297            "delete_saved_simulation".to_string(),
3298            Some(id.clone()),
3299            Some("simulation".to_string()),
3300            serde_json::json!({
3301                "simulation_id": id
3302            }),
3303        )
3304        .await;
3305
3306    Ok(StatusCode::NO_CONTENT)
3307}
3308
3309/// Visualize a statute in various formats.
3310async fn visualize_statute(
3311    user: auth::AuthUser,
3312    State(state): State<Arc<AppState>>,
3313    Path(id): Path<String>,
3314    Query(query): Query<VizQuery>,
3315) -> Result<impl IntoResponse, ApiError> {
3316    user.require_permission(auth::Permission::ReadStatutes)?;
3317
3318    let statutes = state.statutes.read().await;
3319    let statute = statutes
3320        .iter()
3321        .find(|s| s.id == id)
3322        .ok_or_else(|| ApiError::NotFound(format!("Statute not found: {}", id)))?;
3323
3324    // Create decision tree
3325    let tree = DecisionTree::from_statute(statute)
3326        .map_err(|e| ApiError::Internal(format!("Visualization error: {}", e)))?;
3327
3328    // Get theme
3329    let theme = match query.theme.as_deref() {
3330        Some("dark") => legalis_viz::Theme::dark(),
3331        Some("high_contrast") => legalis_viz::Theme::high_contrast(),
3332        Some("colorblind_friendly") => legalis_viz::Theme::colorblind_friendly(),
3333        _ => legalis_viz::Theme::light(),
3334    };
3335
3336    // Generate visualization based on format
3337    let (content, format_str) = match query.format {
3338        VizFormat::Dot => (tree.to_dot(), "dot"),
3339        VizFormat::Ascii => (tree.to_ascii(), "ascii"),
3340        VizFormat::Mermaid => (tree.to_mermaid(), "mermaid"),
3341        VizFormat::PlantUml => (tree.to_plantuml(), "plantuml"),
3342        VizFormat::Svg => (tree.to_svg_with_theme(&theme), "svg"),
3343        VizFormat::Html => (tree.to_html_with_theme(&theme), "html"),
3344    };
3345
3346    Ok(Json(ApiResponse::new(VisualizationResponse {
3347        statute_id: id,
3348        format: format_str.to_string(),
3349        content,
3350        node_count: tree.node_count(),
3351        discretionary_count: tree.discretionary_count(),
3352    })))
3353}
3354
3355/// Server configuration.
3356#[derive(Debug, Clone)]
3357pub struct ServerConfig {
3358    /// Host to bind to
3359    pub host: String,
3360    /// Port to bind to
3361    pub port: u16,
3362}
3363
3364impl Default for ServerConfig {
3365    fn default() -> Self {
3366        Self {
3367            host: "127.0.0.1".to_string(),
3368            port: 3000,
3369        }
3370    }
3371}
3372
3373impl ServerConfig {
3374    /// Returns the bind address.
3375    pub fn bind_addr(&self) -> String {
3376        format!("{}:{}", self.host, self.port)
3377    }
3378}
3379
3380#[cfg(test)]
3381mod tests {
3382    use super::*;
3383    use axum::body::Body;
3384    use axum::http::Request;
3385    #[allow(unused_imports)]
3386    use legalis_core::{Effect, EffectType};
3387    use tower::ServiceExt;
3388
3389    fn create_test_router() -> Router {
3390        let state = Arc::new(AppState::new());
3391        create_router(state)
3392    }
3393
3394    #[tokio::test]
3395    async fn test_health_check() {
3396        let app = create_test_router();
3397        let response = app
3398            .oneshot(
3399                Request::builder()
3400                    .uri("/health")
3401                    .body(Body::empty())
3402                    .unwrap(),
3403            )
3404            .await
3405            .unwrap();
3406
3407        assert_eq!(response.status(), StatusCode::OK);
3408    }
3409
3410    #[tokio::test]
3411    async fn test_list_statutes_empty() {
3412        let app = create_test_router();
3413        let response = app
3414            .oneshot(
3415                Request::builder()
3416                    .uri("/api/v1/statutes")
3417                    .header("Authorization", "ApiKey lgl_12345678901234567890")
3418                    .body(Body::empty())
3419                    .unwrap(),
3420            )
3421            .await
3422            .unwrap();
3423
3424        assert_eq!(response.status(), StatusCode::OK);
3425    }
3426
3427    #[tokio::test]
3428    async fn test_list_statutes_unauthorized() {
3429        let app = create_test_router();
3430        let response = app
3431            .oneshot(
3432                Request::builder()
3433                    .uri("/api/v1/statutes")
3434                    .body(Body::empty())
3435                    .unwrap(),
3436            )
3437            .await
3438            .unwrap();
3439
3440        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
3441    }
3442
3443    #[tokio::test]
3444    async fn test_statute_search() {
3445        let state = Arc::new(AppState::new());
3446
3447        // Add test statute directly to state
3448        {
3449            let mut statutes = state.statutes.write().await;
3450            statutes.push(
3451                Statute::new(
3452                    "search-test-1",
3453                    "Searchable Statute",
3454                    Effect::new(EffectType::Grant, "Test grant"),
3455                )
3456                .with_jurisdiction("TEST"),
3457            );
3458        }
3459
3460        let app = create_router(state);
3461
3462        let response = app
3463            .oneshot(
3464                Request::builder()
3465                    .uri("/api/v1/statutes/search?title=Searchable")
3466                    .header("Authorization", "ApiKey lgl_12345678901234567890")
3467                    .body(Body::empty())
3468                    .unwrap(),
3469            )
3470            .await
3471            .unwrap();
3472
3473        assert_eq!(response.status(), StatusCode::OK);
3474
3475        let body = axum::body::to_bytes(response.into_body(), usize::MAX)
3476            .await
3477            .unwrap();
3478        let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
3479        assert!(!json["data"]["statutes"].as_array().unwrap().is_empty());
3480    }
3481
3482    #[tokio::test]
3483    async fn test_graphql_integration() {
3484        // GraphQL create and query test - uses GraphQL schema
3485        let state = graphql::GraphQLState::new();
3486        let schema = graphql::create_schema(state);
3487
3488        // Create an admin user for testing
3489        use auth::{AuthMethod, AuthUser, Role};
3490        let admin_user = AuthUser::new(
3491            uuid::Uuid::new_v4(),
3492            "admin".to_string(),
3493            Role::Admin,
3494            AuthMethod::Jwt,
3495        );
3496
3497        let mutation = r#"
3498            mutation {
3499                createStatute(input: {
3500                    id: "graphql-test-1"
3501                    title: "GraphQL Test Statute"
3502                    effectDescription: "Test benefit"
3503                    effectType: "Grant"
3504                    jurisdiction: "TEST"
3505                }) {
3506                    id
3507                    title
3508                }
3509            }
3510        "#;
3511
3512        let request = async_graphql::Request::new(mutation).data(admin_user);
3513        let result = schema.execute(request).await;
3514        assert!(result.errors.is_empty());
3515
3516        let query = r#"
3517            {
3518                statutes {
3519                    id
3520                    title
3521                }
3522            }
3523        "#;
3524
3525        let result = schema.execute(query).await;
3526        assert!(result.errors.is_empty());
3527    }
3528
3529    #[tokio::test]
3530    async fn test_readiness_check() {
3531        let app = create_test_router();
3532        let response = app
3533            .oneshot(
3534                Request::builder()
3535                    .uri("/health/ready")
3536                    .body(Body::empty())
3537                    .unwrap(),
3538            )
3539            .await
3540            .unwrap();
3541
3542        assert_eq!(response.status(), StatusCode::OK);
3543    }
3544
3545    #[tokio::test]
3546    async fn test_metrics_endpoint() {
3547        let app = create_test_router();
3548        let response = app
3549            .oneshot(
3550                Request::builder()
3551                    .uri("/metrics")
3552                    .body(Body::empty())
3553                    .unwrap(),
3554            )
3555            .await
3556            .unwrap();
3557
3558        assert_eq!(response.status(), StatusCode::OK);
3559    }
3560}