using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Dtos.Employees;
using Marco.Pms.Model.Dtos.Roles;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Roles;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels;
using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Roles;
using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Data;
#nullable disable
namespace MarcoBMS.Services.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class RolesController : ControllerBase
    {
        private readonly ApplicationDbContext _context;
        private readonly RolesHelper _rolesHelper;
        private readonly UserHelper _userHelper;
        private readonly PermissionServices _permissionService;
        private readonly ILoggingService _logger;
        private readonly CacheUpdateHelper _cache;

        public RolesController(PermissionServices permissionServices, ApplicationDbContext context, RolesHelper rolesHelper, UserHelper userHelper, ILoggingService logger,
            CacheUpdateHelper cache)
        {
            _context = context;
            _permissionService = permissionServices;
            _rolesHelper = rolesHelper;
            _userHelper = userHelper;
            _logger = logger;
            _cache = cache;
        }

        private Guid GetTenantId()
        {
            return _userHelper.GetTenantId();
        }

        [HttpGet("jobrole")]
        public async Task<IActionResult> GetAllJobRoles()
        {
            Guid TenantId = GetTenantId();
            var roles = await _context.JobRoles.Where(c => c.TenantId == TenantId).Select(x => new JobRoleVM()
            {
                Id = x.Id,
                Name = x.Name,
                Description = x.Description
            }).ToListAsync();
            return Ok(ApiResponse<object>.SuccessResponse(roles, "Success.", 200));
        }

        [HttpPost("jobrole")]
        public async Task<IActionResult> AddJobRole([FromBody] CreateJobRoleDto createJobRoleDto)

        {

            Guid TenantId = GetTenantId();
            if (await _context.JobRoles.AnyAsync(c => c.Name.ToLower() == createJobRoleDto.Name.ToLower() && c.TenantId == TenantId))
            {
                return BadRequest(ApiResponse<object>.ErrorResponse("Role with same name already Exists.", "Role with same name already Exists.", 400));
            }
            else
            {
                JobRole jr = createJobRoleDto.ToJobRoleFromCreateJobRoleDot(TenantId);
                _context.JobRoles.Add(jr);
                await _context.SaveChangesAsync();
                return Ok(ApiResponse<object>.SuccessResponse(jr, "Success.", 200));
            }
        }

        [HttpPut("jobrole/{id}")]
        public async Task<IActionResult> UpdateJobRole(string id, [FromBody] UpdateJobRoleDto updateRoleDto)
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values
                    .SelectMany(v => v.Errors)
                    .Select(e => e.ErrorMessage)
                    .ToList();
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
            }
            if (id != updateRoleDto.Id.ToString())
                return BadRequest(ApiResponse<object>.ErrorResponse("Role ID mismatch", "Role ID mismatch", 400));

            try
            {
                Guid TenantId = GetTenantId();

                JobRole jr = updateRoleDto.ToJobRoleFromUpdateJobRoleDot(TenantId);
                _context.JobRoles.Update(jr);
                await _context.SaveChangesAsync();
                return Ok(ApiResponse<object>.SuccessResponse(jr, "Success.", 200));

            }
            catch (Exception ex)
            {
                return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400));
            }
        }
        [HttpDelete("jobrole/{id}")]
        public async Task<IActionResult> DeleteJobRole(Guid id)
        {
            Guid tenantId = GetTenantId();
            var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync();
            var jobRole = await _context.JobRoles.FirstOrDefaultAsync(r => r.Id == id && r.TenantId == tenantId);
            if (jobRole != null)
            {
                var employee = await _context.Employees.Where(e => e.JobRoleId == jobRole.Id).ToListAsync();
                if (employee.Any())
                {
                    _logger.LogWarning("Employee with ID {LoggedEmployeeId} tries to delete the job role with ID {JobRoleId} which is assigned to one or more employees", LoggedEmployee.Id, jobRole.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Cannot delete the job role because it is assigned to one or more employees.", "Cannot delete the job role because it is assigned to one or more employees.", 400));
                }
                _context.JobRoles.Remove(jobRole);
                await _context.SaveChangesAsync();
                _logger.LogInfo("Employee with ID {LoggedEmployeeId} deleted the job role with ID {JobRoleId}", LoggedEmployee.Id, jobRole.Id);
            }
            else
            {
                _logger.LogWarning("Job role with ID {JobRoleId} not found in database", id);
            }
            return Ok(ApiResponse<object>.SuccessResponse(new { }, "Job role deleted successfully", 200));
        }

        [HttpGet]
        public async Task<IActionResult> GetAllRoles()
        {
            Guid TenantId = GetTenantId();

            var roles = await _context.ApplicationRoles.Where(c => c.TenantId == TenantId).ToListAsync();


            var roleFeaturePermissions = await _context.RolePermissionMappings
                .Join(
                    _context.FeaturePermissions,
                    rfp => rfp.FeaturePermissionId,
                    fp => fp.Id,
                    (rfp, fp) => new { rfp.ApplicationRoleId, FeaturePermission = fp })
                .Join(
                    _context.ApplicationRoles,
                    result => result.ApplicationRoleId,
                    role => role.Id,
                    (result, role) => new { Role = role, result.FeaturePermission })
                .Where(x => x.Role.TenantId == TenantId)  // Filter by TenantId
                .Select(x => new
                {
                    RoleId = x.Role.Id,
                    RoleName = x.Role.Role,
                    FeaturePermission = x.FeaturePermission
                })
                .OrderByDescending(r => r.RoleName)
                .ToListAsync();

            List<ApplicationRolesVM> applicationRoles = new List<ApplicationRolesVM>();

            foreach (var item in roles)
            {
                var rolesVM = new ApplicationRolesVM()
                {
                    Id = item.Id,
                    Role = item.Role,
                    IsSystem = item.IsSystem,
                    Description = item.Description,
                    FeaturePermission = []
                };

                ICollection<FeaturePermission> permissions = roleFeaturePermissions.Where(c => c.RoleId == item.Id)
                    .Select(c => c.FeaturePermission).ToList();

                foreach (var permission in permissions)
                {
                    rolesVM.FeaturePermission.Add(
                        new FeaturePermissionVM()
                        {
                            Id = permission.Id,
                            Description = permission.Description,
                            FeatureId = permission.FeatureId,
                            IsEnabled = permission.IsEnabled,
                            Name = permission.Name
                        });
                }

                applicationRoles.Add(rolesVM);

            }

            return Ok(ApiResponse<object>.SuccessResponse(applicationRoles, "Roles list fetched successfully.", 200));
        }


        [HttpPost]
        public async Task<IActionResult> AddRole([FromBody] CreateApplicationRoleDto createRoleDto)
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values
                    .SelectMany(v => v.Errors)
                    .Select(e => e.ErrorMessage)
                    .ToList();
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
            }

            Guid TenantId = GetTenantId();
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            if (createRoleDto.FeaturesPermission == null || (createRoleDto.FeaturesPermission != null && createRoleDto.FeaturesPermission.Count == 0))
            {
                return BadRequest(ApiResponse<object>.ErrorResponse("Feature Permission is required.", "Feature Permission is required.", 400));
            }
            var hasManageMasterPermission = await _permissionService.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id);
            if (!hasManageMasterPermission)
            {
                return StatusCode(403, ApiResponse<object>.SuccessResponse("Access Denied", "User do not have permission for this action", 403));
            }
            bool roleExists = _context.ApplicationRoles
              .Any(r => r.TenantId == TenantId && r.Role.ToLower() == createRoleDto.Role.ToLower());// assuming role name is unique per tenant
            if (roleExists)
            {
                return BadRequest(ApiResponse<object>.ErrorResponse("Role already exists.", "Role already exists.", 400));
            }
            ApplicationRole role = createRoleDto.ToApplicationRoleFromCreateDto(TenantId);
            _context.ApplicationRoles.Add(role);

            var hasPermission = await _permissionService.HasPermission(PermissionsMaster.ManageTenants, loggedInEmployee.Id);
            foreach (var permission in createRoleDto.FeaturesPermission)
            {
                if (!hasPermission &&
                    permission.Id != PermissionsMaster.ManageTenants)
                {
                    var item = new RolePermissionMappings() { ApplicationRoleId = role.Id, FeaturePermissionId = permission.Id };
                    bool assigned = _context.RolePermissionMappings.Any(c => c.ApplicationRoleId == role.Id && c.FeaturePermissionId == permission.Id);
                    if (permission.IsEnabled && !assigned)
                        _context.RolePermissionMappings.Add(item);
                    else
                        _context.RolePermissionMappings.Remove(item);
                }
            }

            await _context.SaveChangesAsync();
            return Ok(ApiResponse<object>.SuccessResponse(role.ToRoleVMFromApplicationRole(), "Roles created successfully.", 200));
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateRole(Guid id, [FromBody] UpdateApplicationRoleDto updateRoleDto)
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values
                    .SelectMany(v => v.Errors)
                    .Select(e => e.ErrorMessage)
                    .ToList();
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
            }
            if (id != updateRoleDto.Id)
                return BadRequest("Role ID mismatch");


            try
            {
                Guid TenantId = GetTenantId();
                var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync();
                var existingRole = await _context.ApplicationRoles.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id);
                if (existingRole != null && existingRole.IsSystem)
                {
                    _logger.LogWarning("Employee with ID {LoggedEmployeeId} tries to update System-defined application roles {AppcationRoleId}", LoggedEmployee.Id, existingRole.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("System-defined roles cannot be updated", "System-defined roles cannot be updated", 400));
                }
                ApplicationRole role = updateRoleDto.ToApplicationRoleFromUpdateDto(TenantId);

                if (role.TenantId != TenantId)
                    return Unauthorized(ApiResponse<object>.ErrorResponse("You don't have any authority to update role", "You don't have any authority to update role", 401));
                if (existingRole != null)
                {
                    _context.ApplicationRoles.Update(role);
                    await _context.SaveChangesAsync();
                }

                bool modified = false;
                foreach (var permission in updateRoleDto.FeaturesPermission)
                {
                    var item = new RolePermissionMappings() { ApplicationRoleId = role.Id, FeaturePermissionId = permission.Id };
                    bool assigned = _context.RolePermissionMappings.Any(c => c.ApplicationRoleId == role.Id && c.FeaturePermissionId == permission.Id);
                    if (permission.IsEnabled == false && assigned == true)
                    {
                        _context.RolePermissionMappings.Remove(item);
                        modified = true;
                    }
                    else if (permission.IsEnabled && !assigned)
                    {
                        _context.RolePermissionMappings.Add(item);
                        modified = true;
                    }
                    if (item.FeaturePermissionId == Guid.Parse("172fc9b6-755b-4f62-ab26-55c34a330614"))
                    {
                        await _cache.ClearAllProjectIdsByRoleId(id);
                    }
                }
                if (modified)
                    await _context.SaveChangesAsync();

                await _cache.ClearAllPermissionIdsByRoleId(id);

                ApplicationRolesVM response = role.ToRoleVMFromApplicationRole();
                List<FeaturePermission> permissions = await _rolesHelper.GetFeaturePermissionByRoleID(response.Id);
                response.FeaturePermission = permissions.Select(c => c.ToFeaturePermissionVMFromFeaturePermission()).ToList();

                return Ok(ApiResponse<object>.SuccessResponse(response, "Roles perimssions updated.", 200));
            }
            catch (Exception ex)
            {
                var response = new
                {
                    message = ex.Message,
                    detail = ex.StackTrace,
                    statusCode = StatusCodes.Status500InternalServerError
                };
                return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, response, 400));
            }
        }


        [HttpGet("{id}")]
        public async Task<IActionResult> GetRoleById(Guid id)
        {
            Guid TenantId = GetTenantId();

            var role = await _context.ApplicationRoles.FindAsync(id);

            if (role == null)
                return NotFound(ApiResponse<object>.ErrorResponse("Role not found", "Role not found", 404));

            if (role.TenantId != TenantId)
                return Unauthorized(ApiResponse<object>.ErrorResponse("You don't have any authority", "You don't have any authority", 401));

            var featurePermissions = await _context.RolePermissionMappings
                .Where(rfp => rfp.ApplicationRoleId == id)
                .Join(
                    _context.FeaturePermissions,
                    rfp => rfp.FeaturePermissionId,
                    fp => fp.Id,
                    (rfp, fp) => new FeaturePermissionVM()
                    {
                        Id = fp.Id,
                        Name = fp.Name,
                        Description = fp.Description,
                        IsEnabled = fp.IsEnabled,
                        FeatureId = fp.FeatureId
                    })
                .ToListAsync();

            ApplicationRolesVM vm = new ApplicationRolesVM()
            {
                Id = role.Id,
                Role = role.Role,
                IsSystem = role.IsSystem,
                FeaturePermission = featurePermissions
            };

            return Ok(ApiResponse<object>.SuccessResponse(vm, "Roles Perimssions fetched successfully.", 200));

        }
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteApplicationRole(Guid id)
        {
            Guid tenantId = GetTenantId();
            var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync();
            var role = await _context.ApplicationRoles.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id && r.TenantId == tenantId);
            if (role != null)
            {
                if (role.IsSystem)
                {
                    _logger.LogInfo("Employee with ID {LoggedEmployeeId} tries to delete system-defined application role with ID {ApplicationRoleId}", LoggedEmployee.Id, role.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("This role cannot be deleted because it is system-defined.", "This role cannot be deleted because it is system-defined.", 400));
                }
                var employeeRoleMapping = await _context.EmployeeRoleMappings.Where(erm => erm.RoleId == role.Id).ToListAsync();
                if (employeeRoleMapping.Count != 0)
                {
                    _logger.LogInfo("Employee with ID {LoggedEmployeeId} tries to delete application role with ID {ApplicationRoleId} with is assigned to an employee", LoggedEmployee.Id, role.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("This role cannot be deleted because it is currently assigned to employees.", "This role cannot be deleted because it is currently assigned to employees.", 400));
                }
                _context.ApplicationRoles.Remove(role);
                var rolePermissionMapping = await _context.RolePermissionMappings.Where(r => r.ApplicationRoleId == role.Id).ToListAsync();
                if (rolePermissionMapping.Count != 0)
                {
                    _context.RolePermissionMappings.RemoveRange(rolePermissionMapping);
                    _logger.LogInfo("All permissions assigned to the application role with ID {ApplicationRoleId} have been removed.", role.Id);
                }
                await _context.SaveChangesAsync();
                _logger.LogInfo("Employee with ID {LoggedEmployeeId} deleted application role with ID {ApplicationRoleId}", LoggedEmployee.Id, role.Id);
            }
            else
            {
                _logger.LogWarning("Application role with ID {ApplicationRoleId} not found in database", id);
            }
            return Ok(ApiResponse<object>.SuccessResponse(new { }, "Application role is deleted successfully", 200));
        }


        [HttpPost]
        [Route("assign-roles")]
        public async Task<IActionResult> ManageRoles([FromBody] List<EmployeeRoleDot> employeeRoleDots)
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values
                    .SelectMany(v => v.Errors)
                    .Select(e => e.ErrorMessage)
                    .ToList();
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
            }
            var employeesIds = employeeRoleDots.Select(e => e.EmployeeId).Distinct().ToList();
            var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync();
            var employees = await _context.Employees.Where(e => employeesIds.Contains(e.Id)).ToListAsync();

            Guid tenantId = GetTenantId();
            try
            {
                foreach (EmployeeRoleDot role in employeeRoleDots)
                {
                    var employee = employees.Find(e => e.Id == role.EmployeeId && e.IsSystem);
                    if (employee != null)
                    {
                        _logger.LogWarning("Employee with ID {LoggedEmployeeId} tries to assign or remove the application role to System-defined employee with ID {EmployeeId}", LoggedEmployee.Id, employee.Id);
                        return BadRequest(ApiResponse<object>.ErrorResponse("System-defined employee cannot have application roles assigned or removed.", "System-defined employee cannot have application roles assigned or removed.", 400));
                    }
                    EmployeeRoleMapping mapping = role.ToEmployeeRoleMappingFromEmployeeRoleDot(tenantId);

                    var existingItem = await _context.EmployeeRoleMappings.AsNoTracking().SingleOrDefaultAsync(c => c.Id == mapping.Id);

                    if (existingItem == null)
                    {
                        if (role.IsEnabled == true)
                        {
                            _context.EmployeeRoleMappings.Add(mapping);
                            await _cache.AddApplicationRole(role.EmployeeId, [mapping.RoleId], tenantId);
                        }
                    }
                    else if (role.IsEnabled == false)
                    {
                        _context.EmployeeRoleMappings.Remove(existingItem);
                        await _cache.RemoveRoleId(existingItem.EmployeeId, existingItem.RoleId, tenantId);
                        await _cache.ClearAllPermissionIdsByEmployeeID(existingItem.EmployeeId, tenantId);
                    }
                    await _cache.ClearAllProjectIds(role.EmployeeId, tenantId);

                }
                await _context.SaveChangesAsync();
            }
            catch (Exception ex)
            {
                return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400));
            }
            return Ok(ApiResponse<object>.SuccessResponse("success", "Roles modified.", 200));
        }
    }
}
