using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using OmsorgCore.Api.Contracts;
using OmsorgCore.Api.Security;
using OmsorgCore.Application.Abstractions;
using OmsorgCore.Application.Services;
using OmsorgCore.Domain.Enums;
using OmsorgCore.Engine.Events;
using OmsorgCore.Infrastructure.Security;

namespace OmsorgCore.Api.Controllers;

[ApiController]
[Authorize]
[Route("api/users")]
public class UsersController : ControllerBase
{
    private const int DefaultPinValidityDays = 7;
    private const int MinPinValidityDays = 1;
    private const int MaxPinValidityDays = 30;

    private readonly IUserService _userService;
    private readonly IDomainEventDispatcher _dispatcher;
    private readonly PasswordPolicyOptions _passwordPolicyOptions;

    public UsersController(IUserService userService, IDomainEventDispatcher dispatcher, IOptions<PasswordPolicyOptions> passwordPolicyOptions)
    {
        _userService = userService;
        _dispatcher = dispatcher;
        _passwordPolicyOptions = passwordPolicyOptions.Value;
    }

    [HttpGet]
    [RequirePermission(ModuleType.Users, PermissionAction.View)]
    public async Task<ActionResult<IReadOnlyList<UserResponse>>> GetAll(CancellationToken cancellationToken)
    {
        var users = await _userService.GetAllAsync(cancellationToken);
        return Ok(users.Select(ToResponse).ToList());
    }

    [HttpPost]
    [RequirePermission(ModuleType.Users, PermissionAction.Create)]
    public async Task<ActionResult<UserResponse>> Create(CreateUserRequest request, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(request.Username) || request.Username.Length > 100)
        {
            return BadRequest("Username ist erforderlich und darf maximal 100 Zeichen lang sein.");
        }

        var (parseError, mode, pinValidity) = ParseModeAndPinValidity(request.Mode, request.PinValidityDays, request.InitialPassword);
        if (parseError is not null)
        {
            return BadRequest(parseError);
        }

        var result = await _userService.CreateForEmployeeAsync(
            request.EmployeeId, request.Username, request.RoleId, mode,
            request.InitialPassword, pinValidity, cancellationToken);

        if (!result.Success)
        {
            return result.FailureReason switch
            {
                CreateUserFailureReason.EmployeeNotFound => NotFound("Mitarbeiter nicht gefunden."),
                CreateUserFailureReason.RoleNotFound => NotFound("Rolle nicht gefunden."),
                CreateUserFailureReason.EmployeeAlreadyHasAccount => Conflict("Dieser Mitarbeiter hat bereits ein Konto."),
                CreateUserFailureReason.UsernameTaken => Conflict("Dieser Username ist bereits vergeben."),
                CreateUserFailureReason.EmployeeEmailMissing => BadRequest("Für den Einladungs-Modus braucht der Mitarbeiter eine hinterlegte E-Mail-Adresse."),
                CreateUserFailureReason.InitialPasswordRequired => BadRequest("InitialPassword ist im Direct-Modus erforderlich."),
                CreateUserFailureReason.PasswordTooShort => BadRequest($"InitialPassword muss mindestens {_passwordPolicyOptions.MinLength} Zeichen lang sein."),
                _ => BadRequest()
            };
        }

        if (mode == UserCreationMode.Invite && result.Email is not null && result.RawInvitePin is not null)
        {
            await _dispatcher.DispatchAsync(
                new UserInvitedEvent(result.Email, result.RawInvitePin, (int)pinValidity!.Value.TotalDays),
                cancellationToken);
        }

        return CreatedAtAction(nameof(GetAll), new UserResponse(
            result.UserId!.Value, request.Username, request.EmployeeId,
            result.RoleName!, IsActive: true, MustChangePassword: mode == UserCreationMode.Direct));
    }

    [HttpPost("{id:guid}/reset-password")]
    [RequirePermission(ModuleType.Users, PermissionAction.Edit)]
    public async Task<IActionResult> ResetPassword(Guid id, ResetUserPasswordRequest request, CancellationToken cancellationToken)
    {
        var (parseError, mode, pinValidity) = ParseModeAndPinValidity(request.Mode, request.PinValidityDays, request.InitialPassword);
        if (parseError is not null)
        {
            return BadRequest(parseError);
        }

        var result = await _userService.AdminResetPasswordAsync(id, mode, request.InitialPassword, pinValidity, cancellationToken);

        if (!result.Success)
        {
            return result.FailureReason switch
            {
                AdminResetPasswordFailureReason.UserNotFound => NotFound("User nicht gefunden."),
                AdminResetPasswordFailureReason.EmployeeEmailMissing => BadRequest("Für den Einladungs-Modus braucht der Mitarbeiter eine hinterlegte E-Mail-Adresse."),
                AdminResetPasswordFailureReason.InitialPasswordRequired => BadRequest("InitialPassword ist im Direct-Modus erforderlich."),
                AdminResetPasswordFailureReason.PasswordTooShort => BadRequest($"InitialPassword muss mindestens {_passwordPolicyOptions.MinLength} Zeichen lang sein."),
                _ => BadRequest()
            };
        }

        if (mode == UserCreationMode.Invite && result.Email is not null && result.RawInvitePin is not null)
        {
            await _dispatcher.DispatchAsync(
                new UserInvitedEvent(result.Email, result.RawInvitePin, (int)pinValidity!.Value.TotalDays),
                cancellationToken);
        }

        return NoContent();
    }

    [HttpPut("{id:guid}")]
    [RequirePermission(ModuleType.Users, PermissionAction.Edit)]
    public async Task<IActionResult> Update(Guid id, UpdateUserRequest request, CancellationToken cancellationToken)
    {
        var result = await _userService.UpdateAsync(id, request.RoleId, request.IsActive, cancellationToken);

        if (!result.Success)
        {
            return result.FailureReason switch
            {
                UpdateUserFailureReason.UserNotFound => NotFound("User nicht gefunden."),
                UpdateUserFailureReason.RoleNotFound => NotFound("Rolle nicht gefunden."),
                _ => BadRequest()
            };
        }

        return NoContent();
    }

    private static (string? Error, UserCreationMode Mode, TimeSpan? PinValidity) ParseModeAndPinValidity(
        string modeRaw, int? pinValidityDays, string? initialPassword)
    {
        if (!Enum.TryParse<UserCreationMode>(modeRaw, ignoreCase: true, out var mode))
        {
            return ("Mode muss 'Invite' oder 'Direct' sein.", default, null);
        }

        TimeSpan? pinValidity = null;
        if (mode == UserCreationMode.Invite)
        {
            var days = pinValidityDays ?? DefaultPinValidityDays;
            if (days < MinPinValidityDays || days > MaxPinValidityDays)
            {
                return ($"PinValidityDays muss zwischen {MinPinValidityDays} und {MaxPinValidityDays} liegen.", mode, null);
            }

            pinValidity = TimeSpan.FromDays(days);
        }

        if (mode == UserCreationMode.Direct && initialPassword is null)
        {
            return ("InitialPassword ist im Direct-Modus erforderlich.", mode, null);
        }

        return (null, mode, pinValidity);
    }

    [HttpGet("{id:guid}/permission-overrides")]
    [RequirePermission(ModuleType.UserManagement, PermissionAction.View)]
    public async Task<ActionResult<IReadOnlyList<UserPermissionOverrideResponse>>> GetPermissionOverrides(Guid id, CancellationToken cancellationToken)
    {
        var overrides = await _userService.GetPermissionOverridesAsync(id, cancellationToken);
        if (overrides is null)
        {
            return NotFound("User nicht gefunden.");
        }

        return Ok(overrides.Select(o => new UserPermissionOverrideResponse(
            o.Id, o.Module, o.Action, o.Effect, o.Scope)).ToList());
    }

    [HttpPost("{id:guid}/permission-overrides")]
    [RequirePermission(ModuleType.UserManagement, PermissionAction.Edit)]
    public async Task<ActionResult<UserPermissionOverrideResponse>> AddPermissionOverride(
        Guid id, AddUserPermissionOverrideRequest request, CancellationToken cancellationToken)
    {
        var result = await _userService.AddPermissionOverrideAsync(id, request.Module, request.Action, request.Effect, request.Scope, cancellationToken);
        if (!result.Success)
        {
            return result.FailureReason switch
            {
                AddPermissionOverrideFailureReason.UserNotFound => NotFound("User nicht gefunden."),
                _ => BadRequest()
            };
        }

        var o = result.Override!;
        return Ok(new UserPermissionOverrideResponse(o.Id, o.Module, o.Action, o.Effect, o.Scope));
    }

    [HttpDelete("{id:guid}/permission-overrides/{overrideId:guid}")]
    [RequirePermission(ModuleType.UserManagement, PermissionAction.Edit)]
    public async Task<IActionResult> RemovePermissionOverride(Guid id, Guid overrideId, CancellationToken cancellationToken)
    {
        var result = await _userService.RemovePermissionOverrideAsync(id, overrideId, cancellationToken);
        if (!result.Success)
        {
            return result.FailureReason switch
            {
                RemovePermissionOverrideFailureReason.UserNotFound => NotFound("User nicht gefunden."),
                RemovePermissionOverrideFailureReason.OverrideNotFound => NotFound("Override nicht gefunden."),
                _ => BadRequest()
            };
        }

        return NoContent();
    }

    private static UserResponse ToResponse(UserSummary summary)
        => new(summary.Id, summary.Username, summary.EmployeeId, summary.RoleName, summary.IsActive, summary.MustChangePassword);
}
