using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using WebApp.Data;
using WebApp.DTOs;
using WebApp.Entities;
using WebApp.Mapping;

namespace WebApp.Endpoints
{
    public static class AuthEndpoints
    {

        public static RouteGroupBuilder MapAuthEndpoints(this WebApplication app)
        {
            const string GetUserEndpointName = "GetUser";

            var group = app.MapGroup("api/auth")
                                        .WithParameterValidation();

            group.MapPost("/login", async (LoginDto dto, ApplicationDbContext context, GeneralUseHelpers guh) =>
            {
                var user = await context.WebUsers.FirstOrDefaultAsync(u => u.Email == dto.Email);

                string hashedPassword = HashPasswordSHA512(dto.Password);

                if (user == null || user.Password != hashedPassword)
                {
                    return Results.Json(new { message = "Wrong email or password." }, statusCode: 401);
                }

                var token = await guh.CreateNewToken(user.UserId);

                return Results.Ok(new
                {
                    message = "Login successful.",
                    token = token.Value
                });
            });

            group.MapPost("/logout", async (HttpContext httpContext, GeneralUseHelpers guh) =>
            {
                var token = await guh.GetTokenFromHTTPContext(httpContext);

                if (token == null)
                {
                    return Results.Json(new { message = "No valid token found." }, statusCode: 401);
                }

                await guh.DeleteToken(token);

                httpContext.Response.Cookies.Delete("token");

                return Results.Ok(new { success = true });
            });

            group.MapGet("/my_account", async (HttpContext httpContext, GeneralUseHelpers guh) =>
            {
                var token = await guh.GetTokenFromHTTPContext(httpContext);

                if(token == null)
                {
                    return Results.Json(new { message = "No valid token found." }, statusCode: 401);
                }

                User? user = await guh.GetUserFromToken(token);

                if(user == null)
                {
                    return Results.Json(new {message = "No user found."}, statusCode: 404);
                }

                Organisation? org = await guh.GetOrganisationFromUserId(user.UserId);
                if (org is not null) return Results.Ok(user.ToUserSummaryWithOrgIdDto(org.OrganisationId));
                return Results.Ok(user.ToUserSummaryDto());

            })
                .WithName(GetUserEndpointName);

            group.MapGet("/my_events", async (HttpContext httpContext, GeneralUseHelpers guh, ApplicationDbContext context) =>
            {
                var token = await guh.GetTokenFromHTTPContext(httpContext);

                if (token == null)
                {
                    return Results.Json(new { message = "No valid token found." }, statusCode: 401);
                }

                User? user = await guh.GetUserFromToken(token);

                if (user == null)
                {
                    return Results.Json(new { message = "No user found." }, statusCode: 404);
                }

                if(!user.IsOrganisation)
                {

                    var eventIds = await context.EventRegistrations
                        .Where(er => er.UserId == user.UserId)
                        .Select(er => er.EventId)
                        .ToListAsync();

                    var events = await context.Events
                        .Where(e => eventIds.Contains(e.EventId))
                        .Include(e => e.Organisation)
                        .Select(e => e.ToEventSummaryDto())
                        .ToListAsync();

                    return Results.Ok(events);
                }
                else
                {
                    var org = await context.Organisations.FirstOrDefaultAsync(o => o.UserId == user.UserId);

                    if(org == null)
                    {
                        return Results.Json(new { message = "No organisation found for this user." }, statusCode: 404);
                    }

                    var events = await context.Events
                        .Where(e => e.OrganisationId == org.OrganisationId)
                        .Select(e => e.ToEventSummaryDto())
                        .ToListAsync();

                    return Results.Ok(events);
                }


            });

            group.MapPost("/add_skill", async (SingleSkillDto dto, HttpContext httpContext, ApplicationDbContext context, GeneralUseHelpers guh) =>
            {
                // Uzyskaj użytkownika z tokenu
                Token? token = await guh.GetTokenFromHTTPContext(httpContext);
                User? user = await guh.GetUserFromToken(token);

                // Tylko wolontariusze powinno móc dodawać swoje skille
                if (user == null || user.IsOrganisation) {
                    return Results.Json(new { message = "Unauthorized" }, statusCode: 401);
                }

                // Szukamy skilla w bazie o ID takim, jak w otrzymanym DTO
                Skill? skill = await context.Skills.FindAsync(dto.Skill);
                if (skill is null)
                {
                    return Results.Json(new { message = "Skill not found" }, statusCode: 404);
                }

                // Sprawdzamy, czy ten użytkownik nie ma już takiego skilla. Jeżeli ma, nie ma sensu dodawać go kilkukrotnie.
                VolunteerSkill? vs = await context.VolunteerSkills.FirstOrDefaultAsync(v => v.UserId == user.UserId && v.SkillId == dto.Skill);
                if (vs is null)
                {
                    // Nie ma - zatem musimy dodać nowy VolunteerSkill do bazy
                    VolunteerSkill newVs = dto.ToVolunteerSkillEntity(user.UserId);
                    context.VolunteerSkills.Add(newVs);
                    await context.SaveChangesAsync();

                } else
                {
                    // Ma - (ta para UserId <-> SkillId już istnieje w bazie) użytkownik już ma ten skill
                    return Results.Json(new { message = "You already have this skill!" }, statusCode: 400);
                }

                return Results.Json(new { message = "Skill added successfully!" }, statusCode: 201);
            });


            group.MapPost("/remove_skill", async (SingleSkillDto dto, HttpContext httpContext, ApplicationDbContext context, GeneralUseHelpers guh) =>
            {
                // Uzyskaj użytkownika z tokenu
                Token? token = await guh.GetTokenFromHTTPContext(httpContext);
                User? user = await guh.GetUserFromToken(token);

                // Tylko wolontariusze powinien móc usuwac swoje skille
                if (user == null || user.IsOrganisation)
                {
                    return Results.Json(new { message = "Unauthorized" }, statusCode: 401);
                }

                // Szukamy skilla w bazie o ID takim, jak w otrzymanym DTO
                Skill? skill = await context.Skills.FindAsync(dto.Skill);
                if (skill is null)
                {
                    return Results.Json(new { message = "Skill not found" }, statusCode: 404);
                }

                // Sprawdzamy, czy ten użytkownik ma już taki skill. Jeżeli nie ma, nie ma sensu usuwac go kilkukrotnie.
                VolunteerSkill? vs = await context.VolunteerSkills.FirstOrDefaultAsync(v => v.UserId == user.UserId && v.SkillId == dto.Skill);
                if (vs is not null)
                {
                    // Nie ma - zatem musimy dodać nowy VolunteerSkill do bazy
                    VolunteerSkill newVs = dto.ToVolunteerSkillEntity(user.UserId);
                    

                    await context.VolunteerSkills.Where(v => v.SkillId == dto.Skill)
                        .ExecuteDeleteAsync();

                }
                else
                {
                    // Ma - (ta para UserId <-> SkillId już istnieje w bazie) użytkownik już ma ten skill
                    return Results.Json(new { message = "You don't have this skill" }, statusCode: 400);
                }

                return Results.Json(new { message = "Skill deleted successfully!" }, statusCode: 201);
            });

            group.MapGet("/get_skills", async (HttpContext httpContext, ApplicationDbContext context, GeneralUseHelpers guh) =>
            {
                // Uzyskaj użytkownika z tokenu
                Token? token = await guh.GetTokenFromHTTPContext(httpContext);
                User? user = await guh.GetUserFromToken(token);

                // Sprawdź, czy użytkownik istnieje i nie jest organizacją
                if (user == null || user.IsOrganisation)
                {
                    return Results.Json(new { message = "Unauthorized" }, statusCode: 401);
                }

                // Pobierz skille wolontariusza
                var skills = await context.VolunteerSkills
                    .Where(vs => vs.UserId == user.UserId)
                    .Include(vs => vs.Skill)
                    .Select(vs => new
                    {
                        skillId = vs.Skill.SkillId,
                        skillName = vs.Skill.Name
                    })
                    .ToListAsync();

                return Results.Json(skills);
            });

            return group;
        }
        
        static string HashPasswordSHA512(string password)
        {
            using (var sha512 = SHA512.Create())
            {
                byte[] bytes = Encoding.ASCII.GetBytes(password);
                byte[] hash = sha512.ComputeHash(bytes);
                string hashstring = BitConverter.ToString(hash).Replace("-", "").ToLower();

                return hashstring;
            }
        }
    }
}
