using Butter.Dtos;
using Butter.Dtos.User;
using Butter.Types;
using Lactose.Models;
using Lactose.Repositories;
using Lactose.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace Lactose.Controllers;

[ApiController]
[Route("api/[controller]")]
public class AuthController(
    ILogger<AuthController> logger,
    LactoseAuthService authService,
    IUserRepository userRepository,
    IPasswordHasher<User> passwordHasher
) : ControllerBase {
    
    
    [HttpPost("login")]
    public ActionResult<AuthResultDto> Login([FromBody] CredentialsDto userDto) {
        User? user;

        if (userDto.Identifier.Contains('@')) {
            //search for user in database (by email)
            user = userRepository.FindByEmail(userDto.Identifier);
        } else {
            //search for user in database (by username)
            user = userRepository.FindByUsername(userDto.Identifier);
        }
        if (user == null) {
            return NotFound(new AuthResultDto() {
                    Success      = false,
                    ErrorMessage = "User or Password was wrong"
                }
            );
        }
        if (user.BannedAt != null) {
            return StatusCode(StatusCodes.Status100Continue,new AuthResultDto() {
                    Success      = false,
                    ErrorMessage = "User is banned"
                }
            ); 
        }

        if (user.DeletedAt != null) {
            return StatusCode(StatusCodes.Status100Continue,new AuthResultDto() {
                    Success      = false,
                    ErrorMessage = "User is disabled"
                }
            ); 
        }
        PasswordVerificationResult result = passwordHasher.VerifyHashedPassword(user, user.Password, userDto.Password);

        switch (result) {
            case PasswordVerificationResult.Failed: return NotFound(new AuthResultDto() { Success = false, ErrorMessage = "User or Password was wrong"});
            case PasswordVerificationResult.SuccessRehashNeeded:
                user.Password = passwordHasher.HashPassword(user, userDto.Password);
                userRepository.Save();
                break;
            case PasswordVerificationResult.Success: break;
        }

        user.LastLogin = DateTime.Now;
        
        var token = authService.GenerateAccessToken(user);
        var refreshToken = authService.GenerateRefreshToken(user);
        userRepository.Save();
        return new AuthResultDto() {
            UserId       = user.Id,
            Token        = token,
            RefreshToken = refreshToken,
            Success      = true
        };
    }

    //TODO: Switch Guid reply with Authentication Result (giving a complete reason in case of failure or giving the authentication token)
    [HttpPost("register")]
    public ActionResult Register([FromBody] UserRegisterDto dto) {
        logger.LogDebug(
            $"""
             Registering user:
             Username: {dto.Username}
             Email:    {dto.Email}
             """
        );

        if (userRepository.FindByEmail(dto.Email) != null || userRepository.FindByUsername(dto.Username) != null) {
            return Conflict("User already exists");
        }

        var user = new User {
            Username    = dto.Username,
            Email       = dto.Email,
            AccessLevel = EAccessLevel.User,
            CreatedAt   = DateTime.Now,
            UpdatedAt   = DateTime.Now
        };

        user.Password = passwordHasher.HashPassword(user, dto.Password);

        userRepository.Insert(user);
        userRepository.Save();
        return Ok(user.Id);
    }

    [Authorize]
    [HttpPost()]
    public ActionResult<LactoseAuthenticatedUser> Check() {
        // this.User;
        //ClaimsPrincipal           principal = User;
        LactoseAuthenticatedUser? identity  = authService.GetUserData(User);
        return Ok(identity);
    }

    [Authorize]
    [HttpPost("logout")]
    public ActionResult Logout() {
        LactoseAuthenticatedUser? identity = authService.GetUserData(User);

        if (identity == null) { return Unauthorized(); }
        
        //Reset token from the database
        User? user = userRepository.Find(identity.Id);
        if (user == null) { return Unauthorized(); }
        user.RefreshToken = string.Empty;
        user.RefreshTokenExpires = null;
        userRepository.Save();
        
        return Ok();
    }

    
    /*
     * Refresh access Token using the Refresh Token  
     */
    
    [HttpPost("refresh")]
    public ActionResult<AuthResultDto> RefreshToken([FromBody] RefreshDto token) {
        User? user = userRepository.Find(token.UserId);

        if (user == null) {
            return NotFound(
                new AuthResultDto() {
                    Success      = false,
                    ErrorMessage = "No user found"
                }
            );
        }

        if (user.BannedAt != null) {
            return StatusCode(
                StatusCodes.Status100Continue,
                new AuthResultDto() {
                    Success      = false,
                    ErrorMessage = "User is banned"
                }
            );
        }

        if (user.DeletedAt != null) {
            return StatusCode(
                StatusCodes.Status100Continue,
                new AuthResultDto() {
                    Success      = false,
                    ErrorMessage = "User is disabled"
                }
            );
        }
        
        if (!authService.ValidateRefreshToken(user, token.RefreshToken)) {
            return Unauthorized(
                new AuthResultDto() {
                    Success      = false,
                    ErrorMessage = "Invalid refresh token"
                }
            );
        }
        
        var authDto = new AuthResultDto() {
            Success = true,
            Token   = authService.GenerateAccessToken(user)
        };

        // TODO: Decide if we want to change or extend refresh token
        if (user.RefreshTokenExpires > DateTime.Now - TimeSpan.FromMinutes(LactoseAuthService.BaseExpirationTime)) {
            user.RefreshTokenExpires = DateTime.Now.AddMinutes(LactoseAuthService.LongExpirationTime);
            userRepository.Save();
        }

        return authDto;
    }
}
