using System.ComponentModel.DataAnnotations;
using ErsatzTV.Application.Configuration;
using ErsatzTV.Application.FFmpegProfiles;
using ErsatzTV.Application.HDHR;
using ErsatzTV.Controllers.Api.Requests;
using ErsatzTV.Core;
using ErsatzTV.Core.Api.Settings;
using ErsatzTV.Extensions;
using ErsatzTV.Filters;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ApiXmltvBlockBehavior = ErsatzTV.Core.Api.Settings.XmltvBlockBehavior;
using ApiXmltvTimeZone = ErsatzTV.Core.Api.Settings.XmltvTimeZone;
using VmXmltvBlockBehavior = ErsatzTV.Application.Configuration.XmltvBlockBehavior;
using VmXmltvTimeZone = ErsatzTV.Application.Configuration.XmltvTimeZone;

namespace ErsatzTV.Controllers.Api;

[ApiController]
[EndpointGroupName("general")]
[RequiresAuthentication]
public class SettingsController(IMediator mediator) : ControllerBase
{
    // FFmpeg settings

    [HttpGet("/api/v1/settings/ffmpeg", Name = "GetFfmpegSettings")]
    [Tags("Settings")]
    [EndpointSummary("Get global FFmpeg settings")]
    [ProducesResponseType(typeof(FFmpegSettingsResponseModel), StatusCodes.Status200OK)]
    public async Task<FFmpegSettingsResponseModel> GetFfmpeg(CancellationToken cancellationToken)
    {
        FFmpegSettingsViewModel settings = await mediator.Send(new GetFFmpegSettings(), cancellationToken);
        return ProjectToResponseModel(settings);
    }

    [HttpPut("/api/v1/settings/ffmpeg", Name = "UpdateFfmpegSettings")]
    [Tags("Settings")]
    [EndpointSummary("Update global FFmpeg settings")]
    [ProducesResponseType(typeof(FFmpegSettingsResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> UpdateFfmpeg(
        [Required] [FromBody]
        UpdateFFmpegSettingsRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, Unit> result = await mediator.Send(request.ToCommand(), cancellationToken);
        return await result.Match(
            Left: error => Task.FromResult(error.ToErrorResult()),
            Right: async _ =>
            {
                FFmpegSettingsViewModel settings = await mediator.Send(new GetFFmpegSettings(), cancellationToken);
                return (IActionResult)new OkObjectResult(ProjectToResponseModel(settings));
            });
    }

    // Playout settings

    [HttpGet("/api/v1/settings/playout", Name = "GetPlayoutSettings")]
    [Tags("Settings")]
    [EndpointSummary("Get global playout settings")]
    [ProducesResponseType(typeof(PlayoutSettingsResponseModel), StatusCodes.Status200OK)]
    public async Task<PlayoutSettingsResponseModel> GetPlayout(CancellationToken cancellationToken)
    {
        PlayoutSettingsViewModel settings = await mediator.Send(new GetPlayoutSettings(), cancellationToken);
        return ProjectToResponseModel(settings);
    }

    [HttpPut("/api/v1/settings/playout", Name = "UpdatePlayoutSettings")]
    [Tags("Settings")]
    [EndpointSummary("Update global playout settings")]
    [ProducesResponseType(typeof(PlayoutSettingsResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> UpdatePlayout(
        [Required] [FromBody]
        UpdatePlayoutSettingsRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, Unit> result = await mediator.Send(request.ToCommand(), cancellationToken);
        return await result.Match(
            Left: error => Task.FromResult(error.ToErrorResult()),
            Right: async _ =>
            {
                PlayoutSettingsViewModel settings = await mediator.Send(new GetPlayoutSettings(), cancellationToken);
                return (IActionResult)new OkObjectResult(ProjectToResponseModel(settings));
            });
    }

    // XMLTV settings

    [HttpGet("/api/v1/settings/xmltv", Name = "GetXmltvSettings")]
    [Tags("Settings")]
    [EndpointSummary("Get global XMLTV settings")]
    [ProducesResponseType(typeof(XmltvSettingsResponseModel), StatusCodes.Status200OK)]
    public async Task<XmltvSettingsResponseModel> GetXmltv(CancellationToken cancellationToken)
    {
        XmltvSettingsViewModel settings = await mediator.Send(new GetXmltvSettings(), cancellationToken);
        return ProjectToResponseModel(settings);
    }

    [HttpPut("/api/v1/settings/xmltv", Name = "UpdateXmltvSettings")]
    [Tags("Settings")]
    [EndpointSummary("Update global XMLTV settings")]
    [ProducesResponseType(typeof(XmltvSettingsResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> UpdateXmltv(
        [Required] [FromBody]
        UpdateXmltvSettingsRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, Unit> result = await mediator.Send(request.ToCommand(), cancellationToken);
        return await result.Match(
            Left: error => Task.FromResult(error.ToErrorResult()),
            Right: async _ =>
            {
                XmltvSettingsViewModel settings = await mediator.Send(new GetXmltvSettings(), cancellationToken);
                return (IActionResult)new OkObjectResult(ProjectToResponseModel(settings));
            });
    }

    // IPTV settings

    [HttpGet("/api/v1/settings/iptv", Name = "GetIptvSettings")]
    [Tags("Settings")]
    [EndpointSummary("Get IPTV output settings")]
    [ProducesResponseType(typeof(IptvSettingsResponseModel), StatusCodes.Status200OK)]
    public async Task<IptvSettingsResponseModel> GetIptv(CancellationToken cancellationToken)
    {
        IptvSettingsViewModel settings = await mediator.Send(new GetIptvSettings(), cancellationToken);
        return ProjectToResponseModel(settings);
    }

    [HttpPut("/api/v1/settings/iptv", Name = "UpdateIptvSettings")]
    [Tags("Settings")]
    [EndpointSummary("Update IPTV output settings")]
    [ProducesResponseType(typeof(IptvSettingsResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> UpdateIptv(
        [Required] [FromBody]
        UpdateIptvSettingsRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, Unit> result = await mediator.Send(request.ToCommand(), cancellationToken);
        return await result.Match(
            Left: error => Task.FromResult(error.ToErrorResult()),
            Right: async _ =>
            {
                IptvSettingsViewModel settings = await mediator.Send(new GetIptvSettings(), cancellationToken);
                return (IActionResult)new OkObjectResult(ProjectToResponseModel(settings));
            });
    }

    // Scanner settings

    [HttpGet("/api/v1/settings/scanner", Name = "GetScannerSettings")]
    [Tags("Settings")]
    [EndpointSummary("Get library scan cadence")]
    [ProducesResponseType(typeof(ScannerSettingsResponseModel), StatusCodes.Status200OK)]
    public async Task<ScannerSettingsResponseModel> GetScanner(CancellationToken cancellationToken)
    {
        int libraryRefreshInterval = await mediator.Send(new GetLibraryRefreshInterval(), cancellationToken);
        return new ScannerSettingsResponseModel(libraryRefreshInterval);
    }

    [HttpPut("/api/v1/settings/scanner", Name = "UpdateScannerSettings")]
    [Tags("Settings")]
    [EndpointSummary("Update library scan cadence")]
    [ProducesResponseType(typeof(ScannerSettingsResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> UpdateScanner(
        [Required] [FromBody]
        UpdateScannerSettingsRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, Unit> result = await mediator.Send(request.ToCommand(), cancellationToken);
        return await result.Match(
            Left: error => Task.FromResult(error.ToErrorResult()),
            Right: async _ =>
            {
                int libraryRefreshInterval = await mediator.Send(new GetLibraryRefreshInterval(), cancellationToken);
                return (IActionResult)new OkObjectResult(new ScannerSettingsResponseModel(libraryRefreshInterval));
            });
    }

    // Logging settings

    [HttpGet("/api/v1/settings/logging", Name = "GetLoggingSettings")]
    [Tags("Settings")]
    [EndpointSummary("Get per-area minimum log levels")]
    [ProducesResponseType(typeof(LoggingSettingsResponseModel), StatusCodes.Status200OK)]
    public async Task<LoggingSettingsResponseModel> GetLogging(CancellationToken cancellationToken)
    {
        LoggingSettingsViewModel settings = await mediator.Send(new GetLoggingSettings(), cancellationToken);
        return ProjectToResponseModel(settings);
    }

    [HttpPut("/api/v1/settings/logging", Name = "UpdateLoggingSettings")]
    [Tags("Settings")]
    [EndpointSummary("Update per-area minimum log levels")]
    [ProducesResponseType(typeof(LoggingSettingsResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> UpdateLogging(
        [Required] [FromBody]
        UpdateLoggingSettingsRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, Unit> result = await mediator.Send(request.ToCommand(), cancellationToken);
        return await result.Match(
            Left: error => Task.FromResult(error.ToErrorResult()),
            Right: async _ =>
            {
                LoggingSettingsViewModel settings = await mediator.Send(new GetLoggingSettings(), cancellationToken);
                return (IActionResult)new OkObjectResult(ProjectToResponseModel(settings));
            });
    }

    // UI settings

    [HttpGet("/api/v1/settings/ui", Name = "GetUiSettings")]
    [Tags("Settings")]
    [EndpointSummary("Get UI preferences")]
    [ProducesResponseType(typeof(UiSettingsResponseModel), StatusCodes.Status200OK)]
    public async Task<UiSettingsResponseModel> GetUi(CancellationToken cancellationToken)
    {
        UiSettingsViewModel settings = await mediator.Send(new GetUiSettings(), cancellationToken);
        return ProjectToResponseModel(settings);
    }

    [HttpPut("/api/v1/settings/ui", Name = "UpdateUiSettings")]
    [Tags("Settings")]
    [EndpointSummary("Update UI preferences")]
    [ProducesResponseType(typeof(UiSettingsResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> UpdateUi(
        [Required] [FromBody]
        UpdateUiSettingsRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, Unit> result = await mediator.Send(request.ToCommand(), cancellationToken);
        return await result.Match(
            Left: error => Task.FromResult(error.ToErrorResult()),
            Right: async _ =>
            {
                UiSettingsViewModel settings = await mediator.Send(new GetUiSettings(), cancellationToken);
                return (IActionResult)new OkObjectResult(ProjectToResponseModel(settings));
            });
    }

    // HDHR settings

    [HttpGet("/api/v1/settings/hdhr", Name = "GetHdhrSettings")]
    [Tags("Settings")]
    [EndpointSummary("Get HDHomeRun emulation settings")]
    [ProducesResponseType(typeof(HdhrSettingsResponseModel), StatusCodes.Status200OK)]
    public async Task<HdhrSettingsResponseModel> GetHdhr(CancellationToken cancellationToken) =>
        await LoadHdhrSettings(cancellationToken);

    [HttpPut("/api/v1/settings/hdhr", Name = "UpdateHdhrSettings")]
    [Tags("Settings")]
    [EndpointSummary("Update HDHomeRun emulation settings")]
    [ProducesResponseType(typeof(HdhrSettingsResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> UpdateHdhr(
        [Required] [FromBody]
        UpdateHdhrSettingsRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, Unit> result = await mediator.Send(request.ToCommand(), cancellationToken);
        return await result.Match(
            Left: error => Task.FromResult(error.ToErrorResult()),
            Right: async _ => (IActionResult)new OkObjectResult(await LoadHdhrSettings(cancellationToken)));
    }

    private async Task<HdhrSettingsResponseModel> LoadHdhrSettings(CancellationToken cancellationToken)
    {
        int tunerCount = await mediator.Send(new GetHDHRTunerCount(), cancellationToken);
        Guid uuid = await mediator.Send(new GetHDHRUUID(), cancellationToken);
        return new HdhrSettingsResponseModel(tunerCount, uuid);
    }

    private static FFmpegSettingsResponseModel ProjectToResponseModel(FFmpegSettingsViewModel vm) =>
        new(
            vm.FFmpegPath,
            vm.FFprobePath,
            vm.DefaultFFmpegProfileId,
            vm.PreferredAudioLanguageCode,
            vm.UseEmbeddedSubtitles,
            vm.ExtractEmbeddedSubtitles,
            vm.ProbeForInterlacedFrames,
            vm.SaveReports,
            vm.GlobalWatermarkId,
            vm.GlobalFallbackFillerId,
            vm.HlsSegmenterIdleTimeout,
            vm.WorkAheadSegmenterLimit,
            vm.InitialSegmentCount,
            vm.HlsDirectOutputFormat,
            vm.DefaultMpegTsScript);

    private static PlayoutSettingsResponseModel ProjectToResponseModel(PlayoutSettingsViewModel vm) =>
        new(vm.DaysToBuild, vm.SkipMissingItems, vm.ScriptedScheduleTimeoutSeconds);

    private static IptvSettingsResponseModel ProjectToResponseModel(IptvSettingsViewModel vm) =>
        new(vm.BaseUrl);

    private static XmltvSettingsResponseModel ProjectToResponseModel(XmltvSettingsViewModel vm) =>
        new(vm.DaysToBuild, ToApiTimeZone(vm.TimeZone), ToApiBlockBehavior(vm.BlockBehavior));

    private static ApiXmltvTimeZone ToApiTimeZone(VmXmltvTimeZone timeZone) =>
        timeZone switch
        {
            VmXmltvTimeZone.Local => ApiXmltvTimeZone.Local,
            VmXmltvTimeZone.Utc => ApiXmltvTimeZone.Utc,
            _ => throw new ArgumentOutOfRangeException(nameof(timeZone), timeZone, null)
        };

    private static ApiXmltvBlockBehavior ToApiBlockBehavior(VmXmltvBlockBehavior blockBehavior) =>
        blockBehavior switch
        {
            VmXmltvBlockBehavior.SplitTimeEvenly => ApiXmltvBlockBehavior.SplitTimeEvenly,
            VmXmltvBlockBehavior.UseActualTimes => ApiXmltvBlockBehavior.UseActualTimes,
            _ => throw new ArgumentOutOfRangeException(nameof(blockBehavior), blockBehavior, null)
        };

    private static LoggingSettingsResponseModel ProjectToResponseModel(LoggingSettingsViewModel vm) =>
        new(
            vm.DefaultMinimumLogLevel,
            vm.ScanningMinimumLogLevel,
            vm.SchedulingMinimumLogLevel,
            vm.SearchingMinimumLogLevel,
            vm.StreamingMinimumLogLevel,
            vm.HttpMinimumLogLevel);

    private static UiSettingsResponseModel ProjectToResponseModel(UiSettingsViewModel vm) =>
        new(vm.IsDarkMode, vm.Language);
}
