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

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

namespace MesaFabApproval.API.Controllers;

[ApiController]
public class UserController : ControllerBase {
    private readonly ILogger<UserController> _logger;
    private readonly IMonInWorkerClient _monInClient;
    private readonly IMemoryCache _cache;
    private readonly IUserService _userService;

    public UserController(ILogger<UserController> logger,
                          IMonInWorkerClient monInClient,
                          IMemoryCache cache,
                          IUserService userService) {
        _logger = logger ?? throw new ArgumentNullException("ILogger not injected");
        _monInClient = monInClient ?? throw new ArgumentNullException("IMonInWorkerClient not injected");
        _cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected");
        _userService = userService ?? throw new ArgumentNullException("IUserService not injected");
    }

    [HttpGet]
    [Route("/user/loginId")]
    [Authorize]
    public async Task<IActionResult> GetUserByLoginId(string loginId) {
        DateTime start = DateTime.Now;
        bool isArgumentError = false;
        bool isInternalError = false;
        string errorMessage = "";

        try {
            _logger.LogInformation("Attempting to get user by LoginID");

            if (string.IsNullOrWhiteSpace(loginId))
                throw new ArgumentException("LoginID cannot be null or empty");

            User? user = _cache.Get<User>($"user{loginId}");

            if (user is null) {
                user = await _userService.GetUserByLoginId(loginId);

                _cache.Set($"user{loginId}", user, DateTimeOffset.Now.AddDays(1));
            }

            if (user is not null) return Ok(user);

            throw new Exception($"User with LoginID {loginId} not found");
        } catch (ArgumentException ex) {
            isArgumentError = true;
            errorMessage = $"Invalid argument. {ex.Message}";
            return BadRequest(errorMessage);
        } catch (Exception ex) {
            isInternalError = true;
            errorMessage = $"Cannot get user by LoginID, because {ex.Message}";
            return Problem(errorMessage);
        } finally {
            string metricName = "GetUserByLoginId";
            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);
            }
        }
    }

    [HttpGet]
    [Route("/user/userId")]
    [Authorize]
    public async Task<IActionResult> GetUserByUserId(int userId) {
        DateTime start = DateTime.Now;
        bool isArgumentError = false;
        bool isInternalError = false;
        string errorMessage = "";

        try {
            _logger.LogInformation("Attempting to get user by LoginID");

            if (userId <= 0) throw new ArgumentException($"{userId} is not a valid user ID");

            User? user = _cache.Get<User>($"user{userId}");

            if (user is null) {
                user = await _userService.GetUserByUserId(userId);

                _cache.Set($"user{userId}", user, DateTimeOffset.Now.AddDays(1));
            }

            if (user is not null) return Ok(user);

            throw new Exception($"User with UserID {userId} not found");
        } catch (ArgumentException ex) {
            isArgumentError = true;
            errorMessage = $"Invalid argument. {ex.Message}";
            return BadRequest(errorMessage);
        } catch (Exception ex) {
            isInternalError = true;
            errorMessage = $"Cannot get user by User ID, because {ex.Message}";
            return Problem(errorMessage);
        } finally {
            string metricName = "GetUserByUserId";
            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);
            }
        }
    }

    [HttpGet]
    [Route("/users/active")]
    [Authorize]
    public async Task<IActionResult> GetAllActiveUsers() {
        DateTime start = DateTime.Now;
        bool isInternalError = false;
        string errorMessage = "";

        try {
            _logger.LogInformation("Attempting to get all active users");

            IEnumerable<User>? activeUsers = _cache.Get<IEnumerable<User>>($"activeUsers");

            if (activeUsers is null) {
                activeUsers = await _userService.GetAllActiveUsers();

                _cache.Set($"activeUsers", activeUsers, DateTimeOffset.Now.AddDays(1));
            }

            if (activeUsers is not null) return Ok(activeUsers);

            throw new Exception($"No active users found");
        } catch (Exception ex) {
            isInternalError = true;
            errorMessage = $"Cannot get all active users, because {ex.Message}";
            return Problem(errorMessage);
        } finally {
            string metricName = "GetAllActiveUsers";
            DateTime end = DateTime.Now;
            double millisecondsDiff = (end - start).TotalMilliseconds;
            _monInClient.PostAverage(metricName + "Latency", millisecondsDiff);

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

    [HttpGet]
    [Route("/approver")]
    [Authorize]
    public async Task<IActionResult> GetApproverUserIdsForSubRoleCategoryItem(string subRoleCategoryItem) {
        DateTime start = DateTime.Now;
        bool isArgumentError = false;
        bool isInternalError = false;
        string errorMessage = "";

        try {
            _logger.LogInformation("Attempting to get approver user IDs");

            if (string.IsNullOrWhiteSpace(subRoleCategoryItem))
                throw new ArgumentException("SubRoleCategoryItem cannot be null or empty");

            IEnumerable<int>? approverUserIds = _cache.Get<IEnumerable<int>>($"approvers{subRoleCategoryItem}");

            if (approverUserIds is null) {
                approverUserIds = await _userService.GetApproverUserIdsBySubRoleCategoryItem(subRoleCategoryItem);

                _cache.Set($"approvers{subRoleCategoryItem}", approverUserIds, DateTimeOffset.Now.AddDays(1));
            }

            if (approverUserIds is not null) return Ok(approverUserIds);

            throw new Exception($"Approvers for SubRoleCategoryItem {subRoleCategoryItem} not found");
        } catch (ArgumentException ex) {
            isArgumentError = true;
            errorMessage = $"Invalid argument. {ex.Message}";
            return BadRequest(errorMessage);
        } catch (Exception ex) {
            isInternalError = true;
            errorMessage = $"Cannot get approver user IDs, because {ex.Message}";
            return Problem(errorMessage);
        } finally {
            string metricName = "GetApproverUserIds";
            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);
            }
        }
    }
}