using DigitalData.Core.Abstractions.Application;
using DigitalData.UserManager.Application.Contracts;
using DigitalData.UserManager.Application.DTOs;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using System.Diagnostics.CodeAnalysis;
using System.Security.Claims;
using DigitalData.UserManager.Application;
using Microsoft.Extensions.Localization;
using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Authorization;

namespace DigitalData.UserManager.API.Controllers
{
    [Route("api/[controller]")]
    [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
    [Authorize]
    public class DirectoryController : ControllerBase
    {
        private readonly IUserService _userService;
        private readonly IDirectorySearchService _dirSearchService;
        private readonly Dictionary<string, string> _customSearchFilters;
        private readonly IStringLocalizer<Resource> _localizer;
        private readonly ILogger<DirectoryController> _logger;

        public DirectoryController(IConfiguration configuration, IStringLocalizer<Resource> localizer, IUserService userService, IDirectorySearchService directorySearchService, ILogger<DirectoryController> logger)
        {
            _localizer = localizer;
            _userService = userService;
            _dirSearchService = directorySearchService;

            var customSearchFiltersSection = configuration.GetSection("DirectorySearch:CustomSearchFilters");
            _customSearchFilters = customSearchFiltersSection.Get<Dictionary<string, string>>() ?? new();
            _logger = logger;
        }

        [HttpGet("Root/{username}")]
        public IActionResult GetRootOf(string username)
        {
            try
            {
                var root = _dirSearchService.GetSearchRootCache(username);

                return root is null ? NotFound() : Ok(new
                {
                    guid = root.Guid,
                    nativeGuid = root.NativeGuid,
                    name = root.Name,
                    path = root.Path,
                    parentPath = root.Parent?.Path,
                    username = root.Username,
                    schemaClassName = root.SchemaClassName
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "{Message}", ex.Message);
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

        [HttpGet("CustomSearchFilter")]
        public IActionResult GetAllCustomFilters(string? filtername)
        {
            try
            {
                if (filtername is null)
                {
                    return Ok(_customSearchFilters);
                }
                else
                {
                    _dirSearchService.CustomSearchFilters.TryGetValue(filtername, out string? filter);
                    return filter is null ? NotFound() : Ok(filter);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "{Message}", ex.Message);
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

        [HttpPost("CreateSearchRoot")]
        public async Task<IActionResult> CreateSearchRoot([FromBody] SearchRootCreateDto searchRootCreateDto)
        {
            try
            {
                var dirEntryUsername = searchRootCreateDto.DirEntryUsername ?? CurrentUser;
                if (dirEntryUsername is null)
                    return Unauthorized();

                bool isValid = _dirSearchService.ValidateCredentials(dirEntryUsername, searchRootCreateDto.DirEntryPassword);

                if (!isValid)
                    return Unauthorized(Result.Fail().Message(_localizer[Key.UserNotFound]));

                var userResult = await _userService.ReadByUsernameAsync(dirEntryUsername);
                if (!userResult.IsSuccess || userResult.Data is null)
                    return Unauthorized(Result.Fail().Message(_localizer[Key.UserNotFoundInLocalDB]));

                _dirSearchService.SetSearchRootCache(userResult.Data.Username, searchRootCreateDto.DirEntryPassword);
                return Ok();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "{Message}", ex.Message);
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

        [HttpGet("SearchByFilter/{filter}")]
        public IActionResult SearchByFilter([FromRoute] string filter, string? dirEntryUsername, params string[] propName)
        {
            try
            {
                dirEntryUsername ??= CurrentUser;

                if (dirEntryUsername is null)
                    return Unauthorized();

                return _dirSearchService.FindAllByUserCache(dirEntryUsername, filter, properties: propName).Then(Ok, IActionResult (m, n) =>
                {
                    _logger.LogNotice(n);
                    return StatusCode(StatusCodes.Status424FailedDependency);
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "{Message}", ex.Message);
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

        [HttpGet("SearchByFilterName/{filterName}")]
        public IActionResult SearchByFilterName([FromRoute] string filterName, string? dirEntryUsername, params string[] propName)
        {
            try
            {
                dirEntryUsername ??= CurrentUser;

                if (dirEntryUsername is null)
                    return Unauthorized();

                _dirSearchService.CustomSearchFilters.TryGetValue(filterName, out string? filter);

                if (filter is null)
                    return NotFound($"The filter named {filterName} does not exist.");

                return _dirSearchService.FindAllByUserCache(dirEntryUsername, filter, properties: propName).Then(Ok, IActionResult (m, n) =>
                {
                    _logger.LogNotice(n);
                    return StatusCode(StatusCodes.Status424FailedDependency);
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "{Message}", ex.Message);
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

        [HttpGet("Group")]
        public IActionResult GetGroups(string? dirEntryUsername, params string[] propName)
        {
            try
            {
                dirEntryUsername ??= CurrentUser;

                if (dirEntryUsername is null)
                    return Unauthorized();

                _dirSearchService.CustomSearchFilters.TryGetValue("Group", out string? filter);

                if (filter is null)
                    throw new InvalidOperationException("The LDAP Group Search filter configuration is missing in your appsettings. Please ensure it's added under DirectorySearch:CustomSearchFilters:Group to enable group searches.");

                return _dirSearchService.FindAllByUserCache(username: dirEntryUsername, filter, properties: propName).Then(Ok, IActionResult (m, n) =>
                {
                    _logger.LogNotice(n);
                    return StatusCode(StatusCodes.Status424FailedDependency);
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "{Message}", ex.Message);
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

        [HttpGet("User")]
        public IActionResult GetUsersByGroupName(string? dirEntryUsername, [FromQuery] string? groupName = null)
        {
            try
            {
                string[] propName = { "memberof", "samaccountname", "givenname", "sn", "mail" };
                dirEntryUsername ??= CurrentUser;

                if (dirEntryUsername is null)
                    return Unauthorized();

                _dirSearchService.CustomSearchFilters.TryGetValue("User", out string? filter);

                if (filter is null)
                    throw new InvalidOperationException("The LDAP User Search filter configuration is missing in your appsettings. Please ensure it's added under DirectorySearch:CustomSearchFilters:User to enable group searches.");

                return _dirSearchService.FindAllByUserCache(username: dirEntryUsername, filter, properties: propName).Then(
                    Success: data =>
                    {
                        if (groupName is not null)
                            data = data
                                .Where(rp => rp.PropertyNames.Cast<string>().Contains("memberof") &&
                                    rp["memberof"].Cast<string>().Any(ldapDir => ldapDir.Contains(groupName)))
                                .ToList();
                        return Ok(data);
                    },
                    Fail: IActionResult (m, n) =>
                    {
                        _logger.LogNotice(n);
                        return StatusCode(StatusCodes.Status424FailedDependency);
                    });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "{Message}", ex.Message);
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

        private string? CurrentUser
        {
            get => (HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value);
        }
    }
}