using DevExpress.Office.Crypto;
using DevExpress.Xpo.DB;
using DevExpress.XtraPrinting;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json;
using TIAMWebApp.Shared.Application.Models;
using TIAMWebApp.Shared.Application.Models.PageModels;
using TIAMWebApp.Server.Models;
using System.Text;
using AyCode.Core.Logger;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using TIAM.Database.DataLayers.Users;
using AyCode.Utils.Helpers;
using TIAM.Entities.Users;
using TIAM.Models.Dtos.Users;
using TIAMWebApp.Server.ModelsTIAMWebApp.Shared.Application.Models;
using TIAMWebApp.Shared.Application.Utility;

namespace TIAMWebApp.Server.Controllers
{
    [Authorize]
    [ApiController]
    [Route("api/v1/[controller]")]
    public class UserAPIController : ControllerBase
    {
        private UserDal _userDal;
        private readonly IConfiguration _configuration;
        private readonly IWebHostEnvironment _webHostEnvironment;
        readonly PasswordHasher _hasher = new();


        /*private UserModel[] users = new UserModel[]
        {
            new UserModel(new Guid("540271f6-c604-4c16-8160-d5a7cafedf00"), "test@tiam.hu", "+36701234567", "Asdasd123456"),
            new UserModel(new Guid("4cbaed43-2465-4d99-84f1-c8bc6b7025f7"), "adam@tiam.hu", "+36701234567", "Asdasd987654")
            
        };*/

        private readonly ILogger<UserAPIController> _logger;

        public UserAPIController(ILogger<UserAPIController> logger, IConfiguration configuration, IWebHostEnvironment webHostEnvironment, UserDal userDal)
        {
            _logger = logger;
            _configuration = configuration;
            _webHostEnvironment = webHostEnvironment;
            _userDal = userDal;
        }


        [AllowAnonymous]
        [HttpPost]
        [Route(APIUrls.AuthenticateUserRouteName)]
        public async Task<IActionResult> AuthenticateUser([FromBody] JsonElement SerializedLoginModel)
        {
            Console.WriteLine("AuthenticateUser called");
            var authenticateUser = JObject.Parse(SerializedLoginModel.GetRawText()).ToObject<LoginModel>();

            //check if userModel exists
            //var userModel = await _userManager.FindByNameAsync(authenticateUser.UserName);
            //if (userModel == null) return Unauthorized();
            Console.WriteLine(authenticateUser.Email);

            //var dbUser = await GetUserByEmail(authenticateUser.Email);
            var dbUser = await _userDal.GetUserByEmailAsync(authenticateUser.Email);

            //check if password is valid
            //bool isValidUser = await _userManager.CheckPasswordAsync(userModel, authenticateUser.Password);

            //mocking
            if (dbUser is null)
            {
                return Unauthorized();
            }
            else
            {
                var isValidUser = false;

                if (dbUser.Password == authenticateUser.Password)
                {
                    Console.WriteLine("Password is valid");
                    isValidUser = true;

                }

                if (isValidUser)
                {
                    Console.WriteLine("UserModel authenticated, let's start JWT");
                    var accessToken = GenerateAccessToken(dbUser);
                    Console.WriteLine("Generate refresh token");
                    var refreshToken = GenerateRefreshToken();
                    dbUser.RefreshToken = refreshToken;
                    //Update userModel with refreshToken!!
                    await _userDal.UpdateJwtRefreshTokenAsync(dbUser.EmailAddress, dbUser.RefreshToken);

                    var response = new MainResponse
                    {
                        Content = new AuthenticationResponse
                        {
                            RefreshToken = refreshToken,
                            AccessToken = accessToken
                        },
                        IsSuccess = true,
                        ErrorMessage = ""
                    };
                    return Ok(response);
                }
                else
                {
                    Console.WriteLine("User not valid");
                    return Unauthorized();
                }
            }
        }

        private string GenerateAccessToken(User user)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            Console.WriteLine("----------------------------------------------------------");

            if (_configuration["JWT:Key"] == null)
                throw new SecurityTokenException("Token is null");

            var keyDetail = Encoding.UTF8.GetBytes(_configuration["JWT:Key"] ?? string.Empty);
            Console.WriteLine(_configuration["JWT:Key"]);

            var claims = new List<Claim>
            {
                new(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new(ClaimTypes.Email, user.EmailAddress)
            };

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Audience = _configuration["JWT:Audience"],
                Issuer = _configuration["JWT:Issuer"],
                Expires = DateTime.UtcNow.AddMinutes(30),
                Subject = new ClaimsIdentity(claims),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(keyDetail), SecurityAlgorithms.HmacSha256Signature)
            };

            var token = tokenHandler.CreateToken(tokenDescriptor) as JwtSecurityToken;
            var writtenToken = tokenHandler.WriteToken(token);
            Console.WriteLine(writtenToken);

            return writtenToken;
        }

        [AllowAnonymous]
        [HttpPost]
        [Route(APIUrls.RefreshTokenRouteName)]
        public async Task<IActionResult> RefreshToken(RefreshTokenRequest? refreshTokenRequest)
        {
            Console.WriteLine("RefreshToken called");

            var response = new MainResponse();
            if (refreshTokenRequest is null)
            {
                Console.WriteLine("RefreshTokenRequest is null");
                response.ErrorMessage = "Invalid  request";
                return BadRequest(response);
            }

            var principal = GetPrincipalFromExpiredToken(refreshTokenRequest.AccessToken);

            if (principal != null)
            {
                Console.WriteLine("Principal is not null");
                var email = principal.Claims.FirstOrDefault(f => f.Type == ClaimTypes.Email);

                //var userModel = await _userManager.FindByEmailAsync(email?.Value);
                //UserModel? user = users.FirstOrDefault(x => x.Email == email?.Value);
                User? dbUser = null;

                if (email != null)
                {
                    //get user from db
                    dbUser = await _userDal.GetUserByEmailAsync(email.Value);
                    Console.WriteLine("DbUser email: " + dbUser?.EmailAddress);
                }

                //mocking - update userModel with new refreshToken so it returns true after the check below
                //dbUser.RefreshToken = refreshTokenRequest.RefreshToken;

                if (dbUser is null || dbUser.RefreshToken != refreshTokenRequest.RefreshToken)
                {
                    response.ErrorMessage = "Invalid Request";
                    Console.WriteLine($"{dbUser?.RefreshToken}, {refreshTokenRequest.RefreshToken}");
                    return BadRequest(response);
                }

                var newAccessToken = GenerateAccessToken(dbUser);
                var refreshToken = GenerateRefreshToken();

                //mocking - update userModel with new refreshToken
                dbUser.RefreshToken = refreshToken;
                //TODO await _userManager.UpdateAsync(userModel);
                await _userDal.UpdateJwtRefreshTokenAsync(dbUser.EmailAddress, dbUser.RefreshToken);

                response.IsSuccess = true;
                response.Content = new AuthenticationResponse
                {
                    RefreshToken = refreshToken,
                    AccessToken = newAccessToken
                };

                return Ok(response);
            }

            Console.WriteLine("Principal is null");
            return NotFound("Invalid Token Found");
        }

        private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
        {
            var tokenHandler = new JwtSecurityTokenHandler();

            if (_configuration["JWT:Key"] == null)
                throw new SecurityTokenException("Token is null");

            var keyDetail = Encoding.UTF8.GetBytes(_configuration["JWT:Key"] ?? string.Empty);

            var tokenValidationParameter = new TokenValidationParameters
            {
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = false,
                ValidateIssuerSigningKey = true,
                ValidIssuer = _configuration["JWT:Issuer"],
                ValidAudience = _configuration["JWT:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(keyDetail),
            };

            var principal = tokenHandler.ValidateToken(token, tokenValidationParameter, out var securityToken);

            if (securityToken is not JwtSecurityToken jwtSecurityToken || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
                throw new SecurityTokenException("Invalid token");

            return principal;
        }

        private string GenerateRefreshToken()
        {

            var randomNumber = new byte[32];
            using (var rng = RandomNumberGenerator.Create())
            {
                rng.GetBytes(randomNumber);
                return Convert.ToBase64String(randomNumber);
            }
        }


        [AllowAnonymous]
        [HttpPost]
        [Route("CreateUser")]
        public async Task<IActionResult> CreateUser([FromBody] JsonElement SerializedRegistrationModel)
        {
            Console.WriteLine("CreateUser called");

            if (string.IsNullOrEmpty(SerializedRegistrationModel.GetRawText()))
            {
                return BadRequest("SerializedLoginModel is required");
            }
            else
            {
                var user = JObject.Parse(SerializedRegistrationModel.GetRawText()).ToObject<RegistrationModel>();

                if (user != null)
                {
                    //add userModel to users array
                    //Array.Resize(ref users, users.Length + 1);
                    //users[users.Length - 1] = new UserModel(user.Email, user.PhoneNumber, user.Password);


                    var userId = Guid.NewGuid();
                    var email = user?.Email;
                    var phoneNumber = user?.PhoneNumber;
                    var password = user?.Password;

                    if (email is null || phoneNumber is null || password is null)
                    {
                        return BadRequest("Invalid request");
                    }
                    else
                    {
                        Console.WriteLine($"User to be created: {userId}");
                        Console.WriteLine($"User to be created: {email}");
                        Console.WriteLine($"User to be created: {phoneNumber}");
                        Console.WriteLine($"User to be created: {password}");

                        await _userDal.CreateUserAsync(new User(userId, email, phoneNumber, password));
                    }
                }

                return Ok("yes");
            }
        }

        [HttpPost]
        [Route("Test1")]
        public async Task<IActionResult> TestEndpoint([FromBody] int testParam)
        {
            return Ok(testParam.ToString());

        }

        [HttpGet]
        [Route("Test2")]
        public string TestEndpoint2(int testParam)
        {
            return testParam.ToString();

        }

        [AllowAnonymous]
        [HttpGet]
        [Route("GetUsers")]
        public Task<List<UserModelDto>> GetUsers()
        {
            //var users = await _userDal.Ctx.Users.ToListAsync();//.GetUsersAsync();
            //return users;
            return _userDal.GetAllUsersModelDtoAsync();
        }

        [AllowAnonymous]
        [HttpGet]
        [Route("GetUserByEmail")]
        public Task<UserModelDto?> GetUserByEmail(string email)
        {
            return _userDal.GetUserModelDtoByEmailAsync(email);
        }

        [AllowAnonymous]
        [HttpPost]
        [Route("GetUserById")]
        public Task<UserModelDto?> GetUserById([FromBody] Guid id)
        {
            Logger.Info($"GetUserById called with id: {id}");
            return _userDal.GetUserModelDtoByIdAsync(id);
        }

        private bool VerifyPassword(string password, string hashedPassword)
        {
            var isPasswordValid = _hasher.VerifyPassword(password, hashedPassword);
            return isPasswordValid;
        }

        private string HashPassword(string password)
        {
            var hashedPassword = _hasher.HashPassword(password);
            return hashedPassword;
        }
    }
}