using System.Security.Authentication;
using System.Security.Principal;

using MesaFabApproval.Shared.Models;
using MesaFabApproval.Shared.Services;

using MesaFabApprovalAPI.Services;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace MesaFabApproval.API.Controllers;

[ApiController]
[AllowAnonymous]
public class AuthenticationController : ControllerBase {
    private readonly ILogger<AuthenticationController> _logger;
    private readonly IMonInWorkerClient _monInClient;
    private readonly IAuthenticationService _authenticationService;

    public AuthenticationController(ILogger<AuthenticationController> logger,
                                    IMonInWorkerClient monInClient,
                                    IAuthenticationService authenticationService) {
        _logger = logger ?? throw new ArgumentNullException("ILogger not injected");
        _monInClient = monInClient ?? throw new ArgumentNullException("IMonInWorkerClient not injected");
        _authenticationService = authenticationService ??
            throw new ArgumentNullException("IAuthenticationService not injected");
    }

    [HttpPost]
    [Route("auth/login")]
    public async Task<IActionResult> Login(AuthAttempt login) {
        DateTime start = DateTime.Now;
        bool isArgumentError = false;
        bool isInternalError = false;
        string errorMessage = "";

        try {
            _logger.LogInformation("Attempting to perform authentication");

            if (login is null) throw new ArgumentNullException("Login cannot be null");

            LoginResult loginResult = await _authenticationService.AuthenticateUser(login);

            if (loginResult.IsAuthenticated)
                return Ok(loginResult);

            return Unauthorized();
        } catch (ArgumentException ex) {
            isArgumentError = true;
            errorMessage = $"Invalid argument. {ex.Message}";
            return BadRequest(errorMessage);
        } catch (Exception ex) {
            isInternalError = true;
            errorMessage = $"Cannot authenticate user, because {ex.Message}";
            return Problem(errorMessage);
        } finally {
            string metricName = "Login";
            DateTime end = DateTime.Now;
            double millisecondsDiff = (end - start).TotalMilliseconds;
            _monInClient.PostAverage(metricName + "Latency", millisecondsDiff);

            if (isArgumentError) {
                _logger.LogWarning(errorMessage);
                _monInClient.PostStatus(metricName, StatusValue.Ok);
            } else if (isInternalError) {
                _logger.LogError(errorMessage);
                _monInClient.PostStatus(metricName, StatusValue.Critical);
            } else {
                _monInClient.PostStatus(metricName, StatusValue.Ok);
            }
        }
    }

    [HttpPost]
    [Route("auth/login/localWindows")]
    public async Task<IActionResult> LoginLocalWindows(WindowsIdentity identity) {
        DateTime start = DateTime.Now;
        bool isArgumentError = false;
        bool isInternalError = false;
        string errorMessage = "";

        try {
            _logger.LogInformation("Attempting to perform local Windows authentication");

            if (identity is null) throw new ArgumentNullException("identity cannot be null");

            LoginResult loginResult = await _authenticationService.AttemptLocalUserAuth(identity);

            if (loginResult.IsAuthenticated)
                return Ok(loginResult);

            return Unauthorized();
        } catch (ArgumentException ex) {
            isArgumentError = true;
            errorMessage = $"Invalid argument. {ex.Message}";
            return BadRequest(errorMessage);
        } catch (Exception ex) {
            isInternalError = true;
            errorMessage = $"Cannot authenticate local Windows user, because {ex.Message}";
            return Problem(errorMessage);
        } finally {
            string metricName = "LoginLocalWindows";
            DateTime end = DateTime.Now;
            double millisecondsDiff = (end - start).TotalMilliseconds;
            _monInClient.PostAverage(metricName + "Latency", millisecondsDiff);

            if (isArgumentError) {
                _logger.LogWarning(errorMessage);
                _monInClient.PostStatus(metricName, StatusValue.Ok);
            } else if (isInternalError) {
                _logger.LogError(errorMessage);
                _monInClient.PostStatus(metricName, StatusValue.Critical);
            } else {
                _monInClient.PostStatus(metricName, StatusValue.Ok);
            }
        }
    }

    [HttpPost]
    [Route("auth/refresh")]
    public async Task<IActionResult> Refresh(AuthAttempt authAttempt) {
        DateTime start = DateTime.Now;
        bool isArgumentError = false;
        bool isInternalError = false;
        string errorMessage = "";

        try {
            _logger.LogInformation("Attempting to refresh auth tokens");

            if (authAttempt is null) throw new ArgumentNullException("AuthAttempt cannot be null");
            if (authAttempt.AuthTokens is null) throw new ArgumentNullException("AuthTokens cannot be null");

            LoginResult loginResult = await _authenticationService.RefreshAuthTokens(authAttempt);

            return Ok(loginResult);
        } catch (ArgumentException ex) {
            isArgumentError = true;
            errorMessage = $"Invalid argument. {ex.Message}";
            return BadRequest(errorMessage);
        } catch (AuthenticationException ex) {
            _logger.LogInformation($"Unable to refresh tokens, because {ex.Message}");
            return Unauthorized();
        } catch (Exception ex) {
            isArgumentError = true;
            errorMessage = $"Cannot authenticate user, because {ex.Message}";
            return Problem(errorMessage);
        } finally {
            string metricName = "RefreshTokens";
            DateTime end = DateTime.Now;
            double millisecondsDiff = (end - start).TotalMilliseconds;
            _monInClient.PostAverage(metricName + "Latency", millisecondsDiff);

            if (isArgumentError) {
                _logger.LogWarning(errorMessage);
                _monInClient.PostStatus(metricName, StatusValue.Ok);
            } else if (isInternalError) {
                _logger.LogError(errorMessage);
                _monInClient.PostStatus(metricName, StatusValue.Critical);
            } else {
                _monInClient.PostStatus(metricName, StatusValue.Ok);
            }
        }
    }

    [HttpOptions]
    [Route("auth/refresh")]
    public IActionResult RefreshOptions() {
        try {
            _logger.LogInformation("Auth refresh options");

            return Ok();
        } catch (Exception ex) {
            _logger.LogError($"Error in auth refresh options. Exception: {ex.Message}");
            throw;
        }
    }
}