using adas_core.Application.Exceptions;
using adas_core.Application.Services.Interfaces;
using adas_core.Authentication.Attributes;
using adas_core.Authentication.Interfaces;
using adas_core.Authentication.Models;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;

namespace adas_core.Controllers;

[ApiController]
[Route("users")]
public class UserController(
    IUserService userService,
    IAuthorityService authorityService,
    Lazy<IPermissionService> permissionService)
    : ControllerBase
{
    /// <summary>
    /// Authenticates a user by validating the provided credentials and returns a token enriched with the user's authorities.
    /// On success, the password is cleared from the returned user payload to avoid exposing sensitive information.
    /// </summary>
    /// <param name="loginInfo">The login payload containing the username and password to authenticate.</param>
    /// <returns>An <see cref="IActionResult"/> containing a successful response with the authenticated <see cref="TokenResult"/>.</returns>
    [AllowAnonymous]
    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginInfo loginInfo)
    {
        var token = await userService.Login(loginInfo.Username, loginInfo.Password);
        var authorities = await authorityService.GetUserAuthorities(token.User.Id);
        token.User.Authorization = authorities;
        token.User.Password = "";
        return Ok(UciResponse<TokenResult>.FromSuccess(token));
    }
    /// <summary>
    /// Generates a new JWT bearer token for the user previously stored in the HTTP context items, enriches it with the user's authorities, and returns it in a success response.
    /// </summary>
    /// <returns>An <see cref="IActionResult"/> containing the generated JWT token and user authorization details wrapped in a <see cref="UciResponse{T}"/>.</returns>
    /// <exception cref="UnauthorizedException">Thrown when no user is found in <c>HttpContext.Items["User"]</c>, indicating the user is not registered.</exception>
    [AllowAnonymous]
    [HttpGet("token")]
    public async Task<IActionResult> LoginWithBearer()
    {
        var user = (User?)HttpContext.Items["User"] ??
                   throw new UnauthorizedException(HttpEnum.ErrorMessage.UnauthorizedUserNotRegistered);
        var newJwtToken = await userService.GenerateJwt(user);
        var authorities = await authorityService.GetUserAuthorities(newJwtToken.User.Id);
        newJwtToken.User.Authorization = authorities;
        return Ok(UciResponse<TokenResult>.FromSuccess(newJwtToken));
    }

    /// <summary>
    /// Authenticates a user by validating the provided access token and returns the associated token details, including its expiry time.
    /// </summary>
    /// <param name="token">The access token supplied via the query string used to perform the login.</param>
    /// <returns>An <see cref="IActionResult"/> containing a successful response with the access token information and its expiry time.</returns>
    [AllowAnonymous]
    [HttpGet("login")]
    public async Task<IActionResult> LoginWithAccessToken([FromQuery] string token)
    {
        var accessToken = await userService.LoginWithAccessToken(token);
        accessToken.AccessToken = token;
        userService.ValidateToken(token, "user", out var tokenRead);
        accessToken.AccessTokenExpiryTime = tokenRead.ValidTo;
        return Ok(UciResponse<TokenResult>.FromSuccess(accessToken));
    }

    /// <summary>
    /// Refreshes the authentication token using the provided refresh token.
    /// Returns a new token wrapped in a success response, allowing anonymous access for token renewal.
    /// </summary>
    /// <param name="refreshToken">The refresh token obtained from a previous authentication request, supplied in the request body.</param>
    /// <returns>An <see cref="IActionResult"/> containing a <see cref="UciResponse{TokenResult}"/> with the refreshed token details on success.</returns>
    [AllowAnonymous]
    [HttpPost("refresh-token")]
    public async Task<IActionResult> RefreshToken([FromBody] string refreshToken)
    {
        var token = await userService.RefreshToken(refreshToken);
        return Ok(UciResponse<TokenResult>.FromSuccess(token));
    }

    /// <summary>
    /// Authenticates a user via a CAS (Central Authentication Service) ticket and returns a JWT token upon success.
    /// </summary>
    /// <param name="service">The service URL that initiated the CAS authentication request.</param>
    /// <param name="ticket">The CAS ticket to validate against the user service.</param>
    /// <returns>A 200 OK response with a JWT token if the user is found; otherwise, a 404 Not Found response.</returns>
    /// <exception cref="UnauthorizedException">Thrown when an error occurs during user lookup or JWT generation, indicating the user is not authorized.</exception>
    [AllowAnonymous]
    [HttpGet("login-cas")]
    public async Task<IActionResult> LoginWithCas([FromQuery] string service, [FromQuery] string ticket)
    {
        try
        {
            var user = await userService.GetUserByCasTicket(service, ticket);
            if (user != null)
            {
                var newJwtToken = await userService.GenerateJwt(user);
                return Ok(UciResponse<TokenResult>.FromSuccess(newJwtToken));
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            throw new UnauthorizedException(HttpEnum.ErrorMessage.UnauthorizedNoPermission);
        }

        return NotFound();
    }

    /// <summary>
    /// Retrieves the currently authenticated user's profile from the HTTP context.
    /// Throws an unauthorized exception when no registered user is found in the request context.
    /// </summary>
    /// <returns>An <see cref="IActionResult"/> containing a successful response with the authenticated <see cref="User"/>.</returns>
    /// <exception cref="UnauthorizedException">Thrown when the user retrieved from the HTTP context items is null, indicating the user is not registered.</exception>
    [HttpGet("me")]
    public IActionResult Me()
    {
        var user = (User?)HttpContext.Items["User"] ??
                   throw new UnauthorizedException(HttpEnum.ErrorMessage.UnauthorizedUserNotRegistered);
        return Ok(UciResponse<User>.FromSuccess(user));
    }

    /// <summary>
    /// Retrieves all users from the system.
    /// </summary>
    /// <returns>An <see cref="IActionResult"/> containing a <see cref="UciResponse{T}"/> with the list of <see cref="User"/> objects retrieved via the user service.</returns>
    [HttpGet]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetAll()
    {
        var users = await userService.GetAll();
        return Ok(UciResponse<List<User>>.FromSuccess(users));
    }


    /// <summary>
    /// Retrieves all available authorities. Requires the AuthSome role and permission to display Source entities.
    /// </summary>
    /// <returns>An <see cref="IActionResult"/> containing a successful response with the list of <see cref="Authorization"/> objects.</returns>
    [HttpGet("authorities")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetAllAuthorities()
    {
        var authorities = await authorityService.GetAllAuthorities();
        return Ok(UciResponse<List<Authorization>>.FromSuccess(authorities));
    }

    /// <summary>
    /// Retrieves a paginated list of users based on the provided pagination filter configuration.
    /// Validates that the configuration payload is supplied and returns the resulting page of users.
    /// </summary>
    /// <param name="config">The pagination filter applied to the user query, supplied in the request body.</param>
    /// <returns>An <see cref="IActionResult"/> containing the paginated users produced by the user service.</returns>
    /// <exception cref="BadRequestException">Thrown when <paramref name="config"/> is <c>null</c>.</exception>
    [HttpPost("paginated")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> PaginatedUsers([FromBody] PaginationFilter config)
    {
        if (config == null) throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestMissingParameters);
        var result = await userService.GetPaginatedUsers(config);
        return Ok(result);
    }

    #region UciPanel
    /// <summary>
    /// Authenticates a user for the administrative panel and returns a token enriched with the user's authorities and panel-specific permissions.
    /// The user's password is cleared from the response before returning.
    /// </summary>
    /// <param name="loginInfo">The login credentials (username and password) used to authenticate the user.</param>
    /// <returns>A successful response containing the authentication token, the user's authorities, and the resolved panel permissions.</returns>
    [AllowAnonymous]
    [HttpPost("login/adm-panel")]
    public async Task<IActionResult> LoginPanel([FromBody] LoginInfo loginInfo)
    {
        var token = await userService.Login(loginInfo.Username, loginInfo.Password);
        var authorities = await authorityService.GetUserAuthorities(token.User.Id);
        token.User.Authorization = authorities;
        token.User.Password = "";
        var result = new TokenResultPanel
        {
            TokenResult = token,
            Permissions = permissionService.Value.GetPermissionsForPanel(token.User)
        };
        return Ok(UciResponse<TokenResultPanel>.FromSuccess(result));
    }
    /// <summary>
    /// Handles a GET request to the "token/adm-panel" endpoint to issue a new JWT token for the panel user resolved from the current HTTP context, enriching it with the user's authorities and panel-specific permissions.
    /// </summary>
    /// <returns>An <see cref="IActionResult"/> containing a successful <see cref="UciResponse{T}"/> of <see cref="TokenResultPanel"/> with the generated token, the user's authorization, and the permissions applicable to the panel.</returns>
    /// <exception cref="UnauthorizedException">Thrown when no user is found in <c>HttpContext.Items["User"]</c>, indicating an unauthorized user that is not registered.</exception>
    [AllowAnonymous]
    [HttpGet("token/adm-panel")]
    public async Task<IActionResult> LoginWithBearerPanel()
    {
        var user = (User?)HttpContext.Items["User"] ??
                   throw new UnauthorizedException(HttpEnum.ErrorMessage.UnauthorizedUserNotRegistered);
        var newJwtToken = await userService.GenerateJwt(user);
        var authorities = await authorityService.GetUserAuthorities(newJwtToken.User.Id);
        newJwtToken.User.Authorization = authorities;
        var result = new TokenResultPanel
        {
            TokenResult = newJwtToken,
            Permissions = permissionService.Value.GetPermissionsForPanel(newJwtToken.User)
        };
        return Ok(UciResponse<TokenResultPanel>.FromSuccess(result));
    }
    /// <summary>
    /// Handles login to the administrative panel by validating the provided access token and returning the authenticated token result along with the user's panel permissions.
    /// </summary>
    /// <param name="token">The access token used to authenticate the user for the administrative panel.</param>
    /// <returns>An <see cref="IActionResult"/> containing a successful response with the token result and the associated panel permissions.</returns>
    [AllowAnonymous]
    [HttpGet("login/adm-panel")]
    public async Task<IActionResult> LoginWithAccessTokenPanel([FromQuery] string token)
    {
        var accessToken = await userService.LoginWithAccessToken(token);
        var result = new TokenResultPanel
        {
            TokenResult = accessToken,
            Permissions = permissionService.Value.GetPermissionsForPanel(accessToken.User)
        };
        return Ok(UciResponse<TokenResultPanel>.FromSuccess(result));
    }

    /// <summary>
    /// Refreshes the authentication token for the administrative panel and returns the new token along with the user's permissions.
    /// </summary>
    /// <param name="refreshToken">The refresh token string provided in the request body used to obtain a new access token.</param>
    /// <returns>An <see cref="IActionResult"/> containing a <see cref="UciResponse{TokenResultPanel}"/> with the refreshed token result and the associated permissions for the panel.</returns>
    [HttpPost("refresh-token/adm-panel")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> RefreshTokenPanel([FromBody] string refreshToken)
    {
        var token = await userService.RefreshToken(refreshToken);
        var result = new TokenResultPanel
        {
            TokenResult = token,
            Permissions = permissionService.Value.GetPermissionsForPanel(token.User)
        };
        return Ok(UciResponse<TokenResultPanel>.FromSuccess(result));
    }

    /// <summary>
    /// Handles a POST request to create a new user by delegating to the user service and returning the created user.
    /// </summary>
    /// <param name="u">The user data submitted in the request used to create the new user.</param>
    /// <returns>An <see cref="IActionResult"/> containing the newly created user in an HTTP 200 OK response.</returns>
    [HttpPost]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> NewUser(User u)
    {
        var newUser = await userService.CreateNewUserByRequest(u);
        return Ok(newUser);
    }

    /// <summary>
    /// Creates a new user along with the associated authorities, enforcing role-based authorization and the required "Source" / "DisplayId" permission checks.
    /// </summary>
    /// <param name="createUserWithAuthDto">The DTO containing the new user's information and the authorities to assign.</param>
    /// <returns>An <see cref="IActionResult"/> containing the created user returned via an HTTP 200 OK response.</returns>
    [HttpPost("authorities")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> NewUserWithAuthorities([FromBody] CreateUserWithAuthDto createUserWithAuthDto)
    {
        var user = await userService.CreateNewUserWithAuthorities(createUserWithAuthDto);
        return Ok(user);
    }

    /// <summary>
    /// Deletes a user identified by <paramref name="userId"/> and removes the associated authority entries.
    /// Returns a successful response when the user is deleted, or throws an exception if the identifier is malformed or the deletion fails.
    /// </summary>
    /// <param name="userId">The string representation of the user's identifier to be deleted.</param>
    /// <returns>An <see cref="IActionResult"/> indicating a successful operation via <see cref="OkResult"/> when the user is removed.</returns>
    /// <exception cref="BadRequestException">Thrown when <paramref name="userId"/> is not a valid identifier format.</exception>
    /// <exception cref="ConflictException">Thrown when the user could not be deleted.</exception>
    [HttpDelete("{userId}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> DeleteUser(string userId)
    {
        var isParsed = ObjectId.TryParse(userId, out var userIdParsed);
        if (!isParsed) throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var isDeleted = await userService.DeleteUser(userIdParsed);

        if (isDeleted)
        {
            await authorityService.DeleteAuthoritiesForUser(userIdParsed);
            return Ok();
        }

        throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
    }

    /// <summary>
    /// Updates an existing user, optionally updating their password when <paramref name="updatePass"/> is <c>true</c>.
    /// </summary>
    /// <param name="u">The user payload containing the updated information to persist.</param>
    /// <param name="updatePass">Indicates whether the password should be updated as part of the operation.</param>
    /// <returns>An <see cref="IActionResult"/> representing a successful update (<c>200 OK</c>).</returns>
    /// <exception cref="ConflictException">Thrown when <c>userService.UpdateUsersByRequest</c> returns <c>null</c>, indicating the update could not be completed.</exception>
    [HttpPut("{updatePass}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> EditUser(User u, bool updatePass)
    {
        _ = await userService.UpdateUsersByRequest(u, updatePass) ??
            throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
        return Ok();
    }

    /// <summary>
    /// Updates an existing user together with their assigned authorities (permissions/roles).
    /// The <paramref name="updatePass"/> flag determines whether the user's password is also updated as part of the operation.
    /// </summary>
    /// <param name="updateUserWithAuthDto">The data transfer object containing the updated user information and authority assignments.</param>
    /// <param name="updatePass">Flag indicating whether the user's password should be updated.</param>
    /// <returns>An <see cref="IActionResult"/> representing an HTTP 200 OK response on successful update.</returns>
    [HttpPut("{updatePass}/authorities")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> EditUserWithAuthorities(UpdateUserWithAuthDto updateUserWithAuthDto,
            bool updatePass)
    {
        await userService.UpdateUserWithAuthorities(updateUserWithAuthDto, updatePass);
        return Ok();
    }

    /// <summary>
    /// Creates a new user authority for the provided authorization request and returns the result.
    /// </summary>
    /// <param name="authorization">The authorization data used to create the new authority.</param>
    /// <returns>An <see cref="IActionResult"/> containing the newly created authority.</returns>
    [HttpPost("authority")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> NewUserAuthority(Authorization authorization)
    {
        var auth = await userService.CreateNewAuthority(authorization);
        return Ok(auth);
    }

    /// <summary>
    /// Deletes a user authority identified by the specified authority identifier.
    /// Requires the caller to have the appropriate role permission and pass the resource-based authorization check.
    /// </summary>
    /// <param name="authorityId">The identifier of the authority to delete.</param>
    /// <returns>An <see cref="IActionResult"/> containing an HTTP 200 OK response upon successful deletion.</returns>
    [HttpDelete("authority/{authorityId}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> DeleteUserAuthority(string authorityId)
    {
        await userService.DeleteAuthority(authorityId);
        return Ok();
    }

    /// <summary>
    /// Deletes the specified user authorities by iterating through the provided list and removing each one.
    /// </summary>
    /// <param name="auths">The list of authority identifiers to be deleted.</param>
    /// <returns>An <see cref="IActionResult"/> indicating the result of the operation; returns <see cref="OkResult"/> upon successful completion.</returns>
    [HttpPut("authorities")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> DeleteUserAuthorities([FromBody] List<string> auths)
    {
        foreach (var auth in auths) await userService.DeleteAuthority(auth);
        return Ok();
    }

    /// <summary>
    /// Updates the password for the specified user. Validates that the provided identifier is a valid format before delegating the password change to the user service.
    /// </summary>
    /// <param name="id">The identifier of the user whose password will be updated.</param>
    /// <param name="updatePasswordDto">The data transfer object containing the current and new password values.</param>
    /// <returns>An <see cref="IActionResult"/> containing the result of the password update operation.</returns>
    /// <exception cref="BadRequestException">Thrown when the provided <paramref name="id"/> is not a valid identifier format.</exception>
    [HttpPost("{id}/password")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> DeleteUserAuthorities(string id, UpdatePasswordDto updatePasswordDto)
    {
        if (!ObjectId.TryParse(id, out var userIdParsed))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var result = await userService.UpdateUserPassword(userIdParsed, updatePasswordDto.OldPassword,
            updatePasswordDto.NewPassword);

        return Ok(result);
    }

    /// <summary>
    /// Updates the authority configuration for a user, restricted to requests authorized against the "Source" entity by its display identifier.
    /// </summary>
    /// <param name="authorization">The authorization details to be applied to the user.</param>
    /// <returns>An <see cref="IActionResult"/> containing an <see cref="Microsoft.AspNetCore.Mvc.OkResult"/> when the authority update succeeds.</returns>
    [HttpPut("authority")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> EditUserAuthority(Authorization authorization)
    {
        await userService.UpdateAuthority(authorization);
        return Ok();
    }

    /// <summary>
    /// Retrieves the list of authorities (permissions) associated with a specific user identifier.
    /// Validates that the provided user identifier is a valid format before querying the authority service.
    /// </summary>
    /// <param name="userId">The string representation of the user's identifier whose authorities are being requested.</param>
    /// <returns>An HTTP 200 response containing a <see cref="UciResponse{T}"/> with the list of <see cref="Authorization"/> objects for the user.</returns>
    /// <exception cref="BadRequestException">Thrown when the provided <paramref name="userId"/> is not a valid ObjectId format.</exception>
    [HttpGet("{userId}/authority")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetAuthorityByUserId(string userId)
    {
        if (!ObjectId.TryParse(userId, out var userIdParsed))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var authorities = await authorityService.GetUserAuthorities(userIdParsed);

        return Ok(UciResponse<List<Authorization>>.FromSuccess(authorities));
    }

    #endregion
}