11using F1 . Core . Dtos ;
22using F1 . Core . Interfaces ;
3+ using F1 . Core . Models ;
34using F1 . Services ;
45using Microsoft . AspNetCore . Mvc ;
6+ using Microsoft . Extensions . Configuration ;
7+ using Microsoft . Extensions . Hosting ;
8+ using System . Collections . Concurrent ;
59using System . Security . Claims ;
610
711namespace F1 . Api . Controllers ;
@@ -10,11 +14,19 @@ namespace F1.Api.Controllers;
1014[ Route ( "selections" ) ]
1115public class SelectionsController : ControllerBase
1216{
17+ private static readonly ConcurrentDictionary < string , Selection > MockSelections = new ( StringComparer . OrdinalIgnoreCase ) ;
1318 private readonly ISelectionService _selectionService ;
19+ private readonly IConfiguration _configuration ;
20+ private readonly IHostEnvironment _hostEnvironment ;
1421
15- public SelectionsController ( ISelectionService selectionService )
22+ public SelectionsController (
23+ ISelectionService selectionService ,
24+ IConfiguration configuration ,
25+ IHostEnvironment hostEnvironment )
1626 {
1727 _selectionService = selectionService ;
28+ _configuration = configuration ;
29+ _hostEnvironment = hostEnvironment ;
1830 }
1931
2032 [ HttpGet ( "{raceId}/config" ) ]
@@ -38,6 +50,11 @@ public async Task<IActionResult> GetMine(string raceId)
3850 return Unauthorized ( ) ;
3951 }
4052
53+ if ( ShouldUseMockCurrentSelections ( ) )
54+ {
55+ return Ok ( GetOrCreateMockSelection ( raceId , userId ) ) ;
56+ }
57+
4158 var selection = await _selectionService . GetSelectionAsync ( raceId , userId ) ;
4259 if ( selection is null )
4360 {
@@ -47,6 +64,25 @@ public async Task<IActionResult> GetMine(string raceId)
4764 return Ok ( selection ) ;
4865 }
4966
67+ [ HttpGet ( "current" ) ]
68+ public async Task < IActionResult > GetCurrent ( )
69+ {
70+ var userId = ResolveUserId ( ) ;
71+ if ( string . IsNullOrWhiteSpace ( userId ) )
72+ {
73+ return Unauthorized ( ) ;
74+ }
75+
76+ if ( ShouldUseMockCurrentSelections ( ) )
77+ {
78+ var selection = GetOrCreateMockSelection ( SelectionService . AustraliaRaceId2026 , userId ) ;
79+ return Ok ( MapCurrentSelections ( selection ) ) ;
80+ }
81+
82+ var selections = await _selectionService . GetCurrentSelectionsAsync ( userId ) ;
83+ return Ok ( selections ) ;
84+ }
85+
5086 [ HttpPut ( "{raceId}/mine" ) ]
5187 public async Task < IActionResult > UpsertMine ( string raceId , [ FromBody ] SelectionSubmissionDto submission )
5288 {
@@ -56,6 +92,18 @@ public async Task<IActionResult> UpsertMine(string raceId, [FromBody] SelectionS
5692 return Unauthorized ( ) ;
5793 }
5894
95+ if ( ShouldUseMockCurrentSelections ( ) )
96+ {
97+ var validationMessage = ValidateMockSubmission ( submission ) ;
98+ if ( validationMessage is not null )
99+ {
100+ return BadRequest ( new { message = validationMessage } ) ;
101+ }
102+
103+ var selection = UpsertMockSelection ( raceId , userId , submission ) ;
104+ return Ok ( selection ) ;
105+ }
106+
59107 try
60108 {
61109 var selection = await _selectionService . UpsertSelectionAsync ( raceId , userId , submission ) ;
@@ -76,4 +124,138 @@ public async Task<IActionResult> UpsertMine(string raceId, [FromBody] SelectionS
76124 return User . FindFirstValue ( ClaimTypes . Email )
77125 ?? Request . Headers [ "Cf-Access-Authenticated-User-Email" ] . FirstOrDefault ( ) ;
78126 }
127+
128+ private bool ShouldUseMockCurrentSelections ( )
129+ {
130+ return _hostEnvironment . IsDevelopment ( )
131+ && _configuration . GetValue < bool > ( "DevSettings:MockCurrentSelections" ) ;
132+ }
133+
134+ private static Selection GetOrCreateMockSelection ( string raceId , string userId )
135+ {
136+ var key = BuildMockSelectionKey ( raceId , userId ) ;
137+ return MockSelections . GetOrAdd ( key , _ => BuildDefaultMockSelection ( raceId , userId ) ) ;
138+ }
139+
140+ private static Selection UpsertMockSelection ( string raceId , string userId , SelectionSubmissionDto submission )
141+ {
142+ var orderedSelections = submission . OrderedSelections ;
143+ var key = BuildMockSelectionKey ( raceId , userId ) ;
144+ var updated = new Selection
145+ {
146+ Id = MockSelections . TryGetValue ( key , out var existing ) ? existing . Id : Guid . NewGuid ( ) ,
147+ RaceId = raceId ,
148+ UserId = userId ,
149+ OrderedSelections = orderedSelections ,
150+ BetType = submission . BetType ,
151+ SubmittedAtUtc = DateTime . UtcNow ,
152+ IsLocked = false
153+ } ;
154+
155+ MockSelections [ key ] = updated ;
156+ return updated ;
157+ }
158+
159+ private static Selection BuildDefaultMockSelection ( string raceId , string userId )
160+ {
161+ return new Selection
162+ {
163+ Id = Guid . NewGuid ( ) ,
164+ RaceId = raceId ,
165+ UserId = userId ,
166+ BetType = BetType . Regular ,
167+ SubmittedAtUtc = new DateTime ( 2026 , 3 , 6 , 9 , 0 , 0 , DateTimeKind . Utc ) ,
168+ IsLocked = false ,
169+ OrderedSelections =
170+ [
171+ new SelectionPosition { Position = 1 , DriverId = "max_verstappen" } ,
172+ new SelectionPosition { Position = 2 , DriverId = "lando_norris" } ,
173+ new SelectionPosition { Position = 3 , DriverId = "charles_leclerc" } ,
174+ new SelectionPosition { Position = 4 , DriverId = "oscar_piastri" } ,
175+ new SelectionPosition { Position = 5 , DriverId = "lewis_hamilton" }
176+ ]
177+ } ;
178+ }
179+
180+ private static IReadOnlyList < CurrentSelectionDto > MapCurrentSelections ( Selection selection )
181+ {
182+ var userName = selection . UserId . Split ( '@' ) [ 0 ] ;
183+ var orderedSelections = selection . OrderedSelections ;
184+ var rows = new List < CurrentSelectionDto > ( orderedSelections . Count ) ;
185+
186+ foreach ( var selectionItem in orderedSelections )
187+ {
188+ var driverId = selectionItem . DriverId ;
189+ if ( string . IsNullOrWhiteSpace ( driverId ) )
190+ {
191+ continue ;
192+ }
193+
194+ rows . Add ( new CurrentSelectionDto
195+ {
196+ Position = selectionItem . Position ,
197+ UserId = selection . UserId ,
198+ UserName = userName ,
199+ DriverId = driverId ,
200+ DriverName = ResolveMockDriverName ( driverId ) ,
201+ SelectionType = selection . BetType . ToString ( ) ,
202+ Timestamp = selection . SubmittedAtUtc
203+ } ) ;
204+ }
205+
206+ return rows ;
207+ }
208+
209+ private static string ResolveMockDriverName ( string driverId )
210+ {
211+ return driverId switch
212+ {
213+ "max_verstappen" => "Max Verstappen" ,
214+ "lando_norris" => "Lando Norris" ,
215+ "charles_leclerc" => "Charles Leclerc" ,
216+ "oscar_piastri" => "Oscar Piastri" ,
217+ "lewis_hamilton" => "Lewis Hamilton" ,
218+ "leclerc" => "Charles Leclerc" ,
219+ "norris" => "Lando Norris" ,
220+ "hamilton" => "Lewis Hamilton" ,
221+ "piastri" => "Oscar Piastri" ,
222+ _ => driverId
223+ } ;
224+ }
225+
226+ private static string ? ValidateMockSubmission ( SelectionSubmissionDto submission )
227+ {
228+ var validSelections = submission . OrderedSelections
229+ . Where ( item => ! string . IsNullOrWhiteSpace ( item . DriverId ) )
230+ . ToList ( ) ;
231+
232+ var distinctPositions = validSelections
233+ . Select ( item => item . Position )
234+ . Distinct ( )
235+ . Count ( ) ;
236+
237+ var distinctCount = submission . OrderedSelections
238+ . Where ( item => ! string . IsNullOrWhiteSpace ( item . DriverId ) )
239+ . Select ( item => item . DriverId )
240+ . Distinct ( StringComparer . OrdinalIgnoreCase )
241+ . Count ( ) ;
242+
243+ var totalCount = submission . OrderedSelections . Count ;
244+ if ( totalCount != 5 || validSelections . Count != 5 || distinctCount != 5 || distinctPositions != 5 )
245+ {
246+ return "Exactly 5 unique drivers must be selected." ;
247+ }
248+
249+ if ( validSelections . Any ( item => item . Position < 1 || item . Position > 5 ) )
250+ {
251+ return "Selection positions must be between 1 and 5." ;
252+ }
253+
254+ return null ;
255+ }
256+
257+ private static string BuildMockSelectionKey ( string raceId , string userId )
258+ {
259+ return $ "{ raceId } ::{ userId } ";
260+ }
79261}
0 commit comments