using Cleanuparr.Api.Features.Arr.Contracts.Requests; using Cleanuparr.Domain.Enums; using Cleanuparr.Infrastructure.Features.Arr.Dtos; using Cleanuparr.Infrastructure.Features.Arr.Interfaces; using Cleanuparr.Persistence; using Cleanuparr.Shared.Helpers; using Mapster; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace Cleanuparr.Api.Features.Arr.Controllers; [ApiController] [Route("api/configuration")] [Authorize] public sealed class ArrConfigController : ControllerBase { private readonly ILogger _logger; private readonly DataContext _dataContext; private readonly IArrClientFactory _arrClientFactory; public ArrConfigController( ILogger logger, DataContext dataContext, IArrClientFactory arrClientFactory) { _logger = logger; _dataContext = dataContext; _arrClientFactory = arrClientFactory; } [HttpGet("sonarr")] public Task GetSonarrConfig() => GetArrConfig(InstanceType.Sonarr); [HttpGet("radarr")] public Task GetRadarrConfig() => GetArrConfig(InstanceType.Radarr); [HttpGet("lidarr")] public Task GetLidarrConfig() => GetArrConfig(InstanceType.Lidarr); [HttpGet("readarr")] public Task GetReadarrConfig() => GetArrConfig(InstanceType.Readarr); [HttpGet("whisparr")] public Task GetWhisparrConfig() => GetArrConfig(InstanceType.Whisparr); [HttpPut("sonarr")] public Task UpdateSonarrConfig([FromBody] UpdateArrConfigRequest request) => UpdateArrConfig(InstanceType.Sonarr, request); [HttpPut("radarr")] public Task UpdateRadarrConfig([FromBody] UpdateArrConfigRequest request) => UpdateArrConfig(InstanceType.Radarr, request); [HttpPut("lidarr")] public Task UpdateLidarrConfig([FromBody] UpdateArrConfigRequest request) => UpdateArrConfig(InstanceType.Lidarr, request); [HttpPut("readarr")] public Task UpdateReadarrConfig([FromBody] UpdateArrConfigRequest request) => UpdateArrConfig(InstanceType.Readarr, request); [HttpPut("whisparr")] public Task UpdateWhisparrConfig([FromBody] UpdateArrConfigRequest request) => UpdateArrConfig(InstanceType.Whisparr, request); [HttpPost("sonarr/instances")] public Task CreateSonarrInstance([FromBody] ArrInstanceRequest request) => CreateArrInstance(InstanceType.Sonarr, request); [HttpPut("sonarr/instances/{id}")] public Task UpdateSonarrInstance(Guid id, [FromBody] ArrInstanceRequest request) => UpdateArrInstance(InstanceType.Sonarr, id, request); [HttpDelete("sonarr/instances/{id}")] public Task DeleteSonarrInstance(Guid id) => DeleteArrInstance(InstanceType.Sonarr, id); [HttpPost("radarr/instances")] public Task CreateRadarrInstance([FromBody] ArrInstanceRequest request) => CreateArrInstance(InstanceType.Radarr, request); [HttpPut("radarr/instances/{id}")] public Task UpdateRadarrInstance(Guid id, [FromBody] ArrInstanceRequest request) => UpdateArrInstance(InstanceType.Radarr, id, request); [HttpDelete("radarr/instances/{id}")] public Task DeleteRadarrInstance(Guid id) => DeleteArrInstance(InstanceType.Radarr, id); [HttpPost("lidarr/instances")] public Task CreateLidarrInstance([FromBody] ArrInstanceRequest request) => CreateArrInstance(InstanceType.Lidarr, request); [HttpPut("lidarr/instances/{id}")] public Task UpdateLidarrInstance(Guid id, [FromBody] ArrInstanceRequest request) => UpdateArrInstance(InstanceType.Lidarr, id, request); [HttpDelete("lidarr/instances/{id}")] public Task DeleteLidarrInstance(Guid id) => DeleteArrInstance(InstanceType.Lidarr, id); [HttpPost("readarr/instances")] public Task CreateReadarrInstance([FromBody] ArrInstanceRequest request) => CreateArrInstance(InstanceType.Readarr, request); [HttpPut("readarr/instances/{id}")] public Task UpdateReadarrInstance(Guid id, [FromBody] ArrInstanceRequest request) => UpdateArrInstance(InstanceType.Readarr, id, request); [HttpDelete("readarr/instances/{id}")] public Task DeleteReadarrInstance(Guid id) => DeleteArrInstance(InstanceType.Readarr, id); [HttpPost("whisparr/instances")] public Task CreateWhisparrInstance([FromBody] ArrInstanceRequest request) => CreateArrInstance(InstanceType.Whisparr, request); [HttpPut("whisparr/instances/{id}")] public Task UpdateWhisparrInstance(Guid id, [FromBody] ArrInstanceRequest request) => UpdateArrInstance(InstanceType.Whisparr, id, request); [HttpDelete("whisparr/instances/{id}")] public Task DeleteWhisparrInstance(Guid id) => DeleteArrInstance(InstanceType.Whisparr, id); [HttpPost("sonarr/instances/test")] public Task TestSonarrInstance([FromBody] TestArrInstanceRequest request) => TestArrInstance(InstanceType.Sonarr, request); [HttpPost("radarr/instances/test")] public Task TestRadarrInstance([FromBody] TestArrInstanceRequest request) => TestArrInstance(InstanceType.Radarr, request); [HttpPost("lidarr/instances/test")] public Task TestLidarrInstance([FromBody] TestArrInstanceRequest request) => TestArrInstance(InstanceType.Lidarr, request); [HttpPost("readarr/instances/test")] public Task TestReadarrInstance([FromBody] TestArrInstanceRequest request) => TestArrInstance(InstanceType.Readarr, request); [HttpPost("whisparr/instances/test")] public Task TestWhisparrInstance([FromBody] TestArrInstanceRequest request) => TestArrInstance(InstanceType.Whisparr, request); private async Task GetArrConfig(InstanceType type) { await DataContext.Lock.WaitAsync(); try { var config = await _dataContext.ArrConfigs .Include(x => x.Instances) .AsNoTracking() .FirstAsync(x => x.Type == type); config.Instances = config.Instances .OrderBy(i => i.Name) .ToList(); return Ok(config.Adapt()); } finally { DataContext.Lock.Release(); } } private async Task UpdateArrConfig(InstanceType type, UpdateArrConfigRequest request) { await DataContext.Lock.WaitAsync(); try { var config = await _dataContext.ArrConfigs .FirstAsync(x => x.Type == type); config.FailedImportMaxStrikes = request.FailedImportMaxStrikes; config.Validate(); await _dataContext.SaveChangesAsync(); return Ok(new { Message = $"{type} configuration updated successfully" }); } catch (Exception ex) { _logger.LogError(ex, "Failed to save {Type} configuration", type); throw; } finally { DataContext.Lock.Release(); } } private async Task CreateArrInstance(InstanceType type, ArrInstanceRequest request) { await DataContext.Lock.WaitAsync(); try { var config = await _dataContext.ArrConfigs .FirstAsync(x => x.Type == type); var instance = request.ToEntity(config.Id); await _dataContext.ArrInstances.AddAsync(instance); await _dataContext.SaveChangesAsync(); return CreatedAtAction(GetConfigActionName(type), new { id = instance.Id }, instance.Adapt()); } catch (Exception ex) { _logger.LogError(ex, "Failed to create {Type} instance", type); throw; } finally { DataContext.Lock.Release(); } } private async Task UpdateArrInstance(InstanceType type, Guid id, ArrInstanceRequest request) { await DataContext.Lock.WaitAsync(); try { var config = await _dataContext.ArrConfigs .Include(c => c.Instances) .FirstAsync(x => x.Type == type); var instance = config.Instances.FirstOrDefault(i => i.Id == id); if (instance is null) { return NotFound($"{type} instance with ID {id} not found"); } request.ApplyTo(instance); await _dataContext.SaveChangesAsync(); return Ok(instance.Adapt()); } catch (Exception ex) { _logger.LogError(ex, "Failed to update {Type} instance with ID {Id}", type, id); throw; } finally { DataContext.Lock.Release(); } } private async Task DeleteArrInstance(InstanceType type, Guid id) { await DataContext.Lock.WaitAsync(); try { var config = await _dataContext.ArrConfigs .Include(c => c.Instances) .FirstAsync(x => x.Type == type); var instance = config.Instances.FirstOrDefault(i => i.Id == id); if (instance is null) { return NotFound($"{type} instance with ID {id} not found"); } config.Instances.Remove(instance); await _dataContext.SaveChangesAsync(); return NoContent(); } catch (Exception ex) { _logger.LogError(ex, "Failed to delete {Type} instance with ID {Id}", type, id); throw; } finally { DataContext.Lock.Release(); } } private async Task TestArrInstance(InstanceType type, TestArrInstanceRequest request) { try { string? resolvedApiKey = null; if (request.ApiKey.IsPlaceholder() && request.InstanceId.HasValue) { var existingInstance = await _dataContext.ArrInstances .AsNoTracking() .FirstOrDefaultAsync(i => i.Id == request.InstanceId.Value); if (existingInstance is null) { return NotFound($"Instance with ID {request.InstanceId.Value} not found"); } resolvedApiKey = existingInstance.ApiKey; } var testInstance = request.ToTestInstance(resolvedApiKey); var client = _arrClientFactory.GetClient(type, request.Version); await client.HealthCheckAsync(testInstance); return Ok(new { Message = $"Connection to {type} instance successful" }); } catch (Exception ex) { _logger.LogError(ex, "Failed to test {Type} instance connection", type); return BadRequest(new { Message = $"Connection failed: {ex.Message}" }); } } private static string GetConfigActionName(InstanceType type) => type switch { InstanceType.Sonarr => nameof(GetSonarrConfig), InstanceType.Radarr => nameof(GetRadarrConfig), InstanceType.Lidarr => nameof(GetLidarrConfig), InstanceType.Readarr => nameof(GetReadarrConfig), InstanceType.Whisparr => nameof(GetWhisparrConfig), _ => nameof(GetSonarrConfig), }; }