using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using MySql.Data.MySqlClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Server;

public class Login : Route
{
    private static string secretKey =
        "stronk-key-much-sercret-much-more-stronk-stronk-key-much-sercret-much-more-stronk";

    public static string GenerateToken(string user)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: "TimeLogServer",
            audience: "TimeLogWebsite",
            claims: new[] { new Claim("user", user) },
            expires: DateTime.Now.AddHours(2),
            signingCredentials: creds
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    public static bool VerifyPassword(string enteredPassword, string storedHash)
    {
        byte[] hashBytes = Convert.FromBase64String(storedHash);

        // Extract the salt from the stored hash
        byte[] salt = new byte[16];
        Array.Copy(hashBytes, 0, salt, 0, 16);

        // Hash the entered password with the stored salt
        using (
            var pbkdf2 = new Rfc2898DeriveBytes(
                enteredPassword,
                salt,
                10000,
                HashAlgorithmName.SHA256
            )
        )
        {
            byte[] newHash = pbkdf2.GetBytes(32);

            // Compare the computed hash with the stored hash
            for (int i = 0; i < 32; i++)
            {
                if (newHash[i] != hashBytes[i + 16])
                    return false;
            }
            return true;
        }
    }

    public static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
    {
        try
        {
            // extract data from body
            string body;
            using (StreamReader bodyReader = new(request.InputStream, request.ContentEncoding))
            {
                body = bodyReader.ReadToEnd();
            }
            JObject jsonObject = JObject.Parse(body);
            string mail = jsonObject["mail"]?.ToString() ?? "";
            string password = jsonObject["password"]?.ToString() ?? "";

            // prepare SQL query
            MySqlCommand cmd = new()
            {
                CommandText =
                    @"SELECT u.id, password FROM User u 
                  INNER JOIN Password p ON p.user=u.id 
                  WHERE mail=@mail;",
            };
            cmd.Parameters.AddWithValue("@mail", mail);

            using (MySqlConnection conn = new(connectionString))
            {
                cmd.Connection = conn;
                conn.Open();
                // execute query and read results
                MySqlDataReader reader = cmd.ExecuteReader();
                string? userId = "";
                string? hashedPass = "";
                string? jsonResponse;
                while (reader.Read())
                {
                    userId = Convert.ToString(reader["id"]);
                    hashedPass = reader.GetString("password");
                }

                // check username
                if (string.IsNullOrEmpty(userId))
                {
                    throw new Exception("Invalid Username or Password");
                }
                //check password
                if (
                    string.IsNullOrEmpty(password)
                    || string.IsNullOrEmpty(hashedPass)
                    || !VerifyPassword(password, hashedPass)
                )
                {
                    throw new Exception("Invalid Username or Password");
                }

                jsonResponse = JsonConvert.SerializeObject(GenerateToken(userId));

                // prepare response
                SendSuccess(response, jsonResponse);
            }
        }
        catch (Exception ex)
        {
            SendError(response, ex);
        }
    }
}
