using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

/// <summary>
/// In-app scenario catalogue and run control for clinical testing sessions.
/// </summary>
[ApiController]
[Route("api/v1/simulation")]
[Produces("application/json")]
[Authorize]
public class SimulationController : ControllerBase
{
    private readonly SimulationOptions _options;
    private readonly ISimulationRunner? _runner;
    private readonly IScenarioCatalog? _catalog;
    private readonly ICurrentUserService _currentUser;
    private readonly IAuditService _audit;

    public SimulationController(
        IOptions<SimulationOptions> options,
        IServiceProvider services,
        ICurrentUserService currentUser,
        IAuditService audit)
    {
        _options = options.Value;
        _runner = services.GetService<ISimulationRunner>();
        _catalog = services.GetService<IScenarioCatalog>();
        _currentUser = currentUser;
        _audit = audit;
    }

    /// <summary>
    /// Feature-detect simulation availability without requiring simulation:run.
    /// Returns enabled=false when the feature is off (never 404).
    /// </summary>
    [HttpGet("config")]
    [AuthorizePermission(ClinicalPermissions.AlertsRead)]
    [ProducesResponseType(typeof(ApiResponse<SimulationConfigResponse>), StatusCodes.Status200OK)]
    public IActionResult GetConfig()
    {
        if (!_options.Enabled)
            return Ok(ApiResponse<SimulationConfigResponse>.Ok(new SimulationConfigResponse(Enabled: false)));

        return Ok(ApiResponse<SimulationConfigResponse>.Ok(new SimulationConfigResponse(
            Enabled: true,
            MaxSpeed: _options.MaxSpeed,
            MaxConcurrentRuns: _options.MaxConcurrentRuns)));
    }

    /// <summary>
    /// Lists available scenario files from the configured scenario directory.
    /// </summary>
    [HttpGet("scenarios")]
    [AuthorizePermission(ClinicalPermissions.SimulationRun)]
    [ProducesResponseType(typeof(ApiResponse<List<ScenarioSummaryResponse>>), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
    public IActionResult ListScenarios()
    {
        EnsureEnabled();
        var items = _catalog!.ListScenarios()
            .Select(s => new ScenarioSummaryResponse(
                s.Scenario.Id,
                s.Scenario.Name,
                s.Scenario.Description,
                s.Scenario.DurationMinutes,
                s.Scenario.Tags,
                s.Encounter.Department,
                s.Events.Count,
                s.ExpectedOutcomes?.Count ?? 0))
            .ToList();
        return Ok(ApiResponse<List<ScenarioSummaryResponse>>.Ok(items));
    }

    /// <summary>
    /// Starts a background scenario replay.
    /// </summary>
    [HttpPost("runs")]
    [AuthorizePermission(ClinicalPermissions.SimulationRun)]
    [ProducesResponseType(typeof(ApiResponse<SimulationRunResponse>), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> StartRun(
        [FromBody] StartSimulationRunRequest req, CancellationToken ct)
    {
        EnsureEnabled();

        var userId = _currentUser.UserId?.ToString()
            ?? throw new ValidationException("Authenticated user id is required.", "SIMULATION_USER_REQUIRED");

        var state = await _runner!.StartAsync(req.ScenarioId, req.Speed, userId, ct);

        await _audit.WriteAsync(
            AuditAction.SimulationRunStarted,
            "SimulationRun",
            state.RunId,
            newValue: new
            {
                state.ScenarioId,
                state.ScenarioName,
                state.Speed,
                StartedBy = userId,
            });

        return StatusCode(201, ApiResponse<SimulationRunResponse>.Created(ToResponse(state)));
    }

    /// <summary>
    /// Lists active and recent simulation runs from the in-memory registry.
    /// </summary>
    [HttpGet("runs")]
    [AuthorizePermission(ClinicalPermissions.SimulationRun)]
    [ProducesResponseType(typeof(ApiResponse<List<SimulationRunResponse>>), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
    public IActionResult ListRuns()
    {
        EnsureEnabled();
        var items = _runner!.ListRuns().Select(ToResponse).ToList();
        return Ok(ApiResponse<List<SimulationRunResponse>>.Ok(items));
    }

    /// <summary>
    /// Gets one simulation run by id.
    /// </summary>
    [HttpGet("runs/{runId:guid}")]
    [AuthorizePermission(ClinicalPermissions.SimulationRun)]
    [ProducesResponseType(typeof(ApiResponse<SimulationRunResponse>), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
    public IActionResult GetRun(Guid runId)
    {
        EnsureEnabled();
        var state = _runner!.GetRun(runId)
            ?? throw new NotFoundException($"Simulation run '{runId}' was not found.");
        return Ok(ApiResponse<SimulationRunResponse>.Ok(ToResponse(state)));
    }

    /// <summary>
    /// Stops an in-flight run. Idempotent — stopping a finished run succeeds as a no-op.
    /// </summary>
    [HttpPost("runs/{runId:guid}/stop")]
    [AuthorizePermission(ClinicalPermissions.SimulationRun)]
    [ProducesResponseType(typeof(ApiResponse<SimulationRunResponse>), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> StopRun(Guid runId)
    {
        EnsureEnabled();

        if (!_runner!.Cancel(runId))
            throw new NotFoundException($"Simulation run '{runId}' was not found.");

        var state = _runner.GetRun(runId)
            ?? throw new NotFoundException($"Simulation run '{runId}' was not found.");

        await _audit.WriteAsync(
            AuditAction.SimulationRunStopped,
            "SimulationRun",
            runId,
            newValue: new
            {
                state.ScenarioId,
                state.Status,
                StoppedBy = _currentUser.UserId?.ToString(),
            });

        return Ok(ApiResponse<SimulationRunResponse>.Ok(ToResponse(state)));
    }

    private void EnsureEnabled()
    {
        if (!_options.Enabled || _runner is null || _catalog is null)
            throw new NotFoundException("Simulation endpoints are not available.");
    }

    private static SimulationRunResponse ToResponse(SimulationRunState state) =>
        new(
            state.RunId,
            state.ScenarioId,
            state.ScenarioName,
            state.Status.ToDbString(),
            state.Speed,
            state.PatientId,
            state.EncounterId,
            state.PatientDisplayName,
            state.StartedAt,
            state.ElapsedRealSeconds,
            state.LastOffsetMinutes,
            state.TotalOffsetMinutes,
            state.ProgressPercent,
            state.ObservationsSent,
            state.MedicationsSent,
            state.OrdersPlaced,
            state.FailureReason);
}
