using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Intrinsics.Arm;
using WebApp.Data;
using WebApp.DTOs;
using WebApp.Entities;
using WebApp.Mapping;

namespace WebApp.Endpoints
{
    public static class EventsEndpoints
    {
        const string GetEventEndpointName = "GetEvent";

        public static RouteGroupBuilder MapEventsEndpoints(this WebApplication app)
        {
            var group = app.MapGroup("api/events")
                                    .WithParameterValidation();

            // GET /events
            group.MapGet("/",
                async (ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
            {

                // Sprawdź, czy lista powinna by posortowana rosnąco. Domyślnie: malejąco.
                var sort = httpContext.Request.Query["sort"].ToString().ToUpper();

                // Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której.
                Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
                Organisation? org = await guhf.GetOrganisationFromToken(token);

                List<EventSummaryDto> result = await guhf.BuildSummaryEventsDto(
                    dbContext,
                    org,
                    (sort == "ASC")
                );

                return Results.Ok(result);

            });


            // GET /events/1
            group.MapGet("/{id}",
                async (int id, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
            {

                Event? Eve = await dbContext
                    .Events
                    .Include(e => e.Organisation)
                    .FirstOrDefaultAsync(e => e.EventId == id);
                if (Eve is null) return Results.NotFound();

                // Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której.
                Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
                Organisation? org = await guhf.GetOrganisationFromToken(token);

                // Jeśli token należy do organizacji, która utworzyła to wydarzenie,
                // to zwróć także EventRegistrations. W przeciwnym razie niech będzie to
                // puste pole.
                List<EventDetailsDto> result = await guhf.BuildDetailedEventsDto(
                    dbContext,
                    org
                );

                return Results.Ok(result.FirstOrDefault(e => e.EventId == id));
            })
            .WithName(GetEventEndpointName);

            // POST /events
            group.MapPost("/", 
                async (EventCreateDto newEvent, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
            {

                // Uzyskaj organizację z tokenu
                Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
                Organisation? org = await guhf.GetOrganisationFromToken(token);
                if (org is null) return Results.Unauthorized();

                // dodajemy id organizacji z tokenu
                Event Eve = newEvent.ToEntity();
                Eve.OrganisationId = org.OrganisationId;

                dbContext.Events.Add(Eve);
                await dbContext.SaveChangesAsync();

                return Results.CreatedAtRoute(
                    GetEventEndpointName, 
                    new { id = Eve.EventId }, 
                    Eve.ToEventDetailsDto()); //EventDetailsDto
            });

            // PUT /events/1
            group.MapPut("/{id}", 
                async (int id, EventUpdateDto updatedEvent, ApplicationDbContext dbContext, GeneralUseHelpers guhf, HttpContext httpContext) =>
            {
                // Uzyskaj organizację z tokenu
                Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
                Organisation? org = await guhf.GetOrganisationFromToken(token);
                if (org is null) return Results.Unauthorized();

                Console.Write(org.OrganisationId);
                var existingEvent = await dbContext.Events.FindAsync(id);
                if (existingEvent is null)
                {
                    return Results.NotFound();
                }

                // Sprawdź, czy organizacja ma prawo
                // do zmodyfikowania tego (EventId = id) eventu.
                if (org.OrganisationId != existingEvent.OrganisationId) return Results.StatusCode(403);

                var originalOrgId = existingEvent.OrganisationId;
                dbContext.Entry(existingEvent)
                         .CurrentValues
                         .SetValues(updatedEvent.ToEntity(id));
                existingEvent.OrganisationId = originalOrgId;

                dbContext.Entry(existingEvent)
                         .Collection(Eve => Eve.EventRegistrations)
                         .IsModified = false;

                await dbContext.SaveChangesAsync();

                return Results.NoContent();
            });

            // DELETE /events/1
            group.MapDelete("/{id}", 
                async (int id, ApplicationDbContext dbContext, GeneralUseHelpers guhf, HttpContext httpContext) =>
            {

                // Uzyskaj organizację z tokenu
                Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
                Organisation? org = await guhf.GetOrganisationFromToken(token);
                if (org is null) return Results.Unauthorized();

                // Sprawdź, czy organizacja ma prawo
                // do usunięcia tego (EventId = id) eventu.
                Event? Eve = await dbContext.Events.FindAsync(id);
                if (Eve is null) return Results.NotFound();
                else if (org.OrganisationId != Eve.OrganisationId) return Results.StatusCode(403);

                await dbContext.Events
                         .Where(Eve => Eve.EventId == id)
                         .ExecuteDeleteAsync();

                return Results.NoContent();
            });

            // POST /events/search
            group.MapPost("/search/",
                async (EventSearchDto query, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
                {

                    // Uzyskaj organizację z tokenu
                    var sort = httpContext.Request.Query["sort"].ToString().ToUpper();
                    Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
                    Organisation? org = await guhf.GetOrganisationFromToken(token);
                    List<EventSummaryDto> SearchCandidates = await guhf.BuildSummaryEventsDto(dbContext, org, sort == "ASC");
                    List<EventSummaryDto> SearchResults = [];


                    foreach(EventSummaryDto e in SearchCandidates)
                    {
                        bool matchFound = true;
                        // Logika wyszukiwania
                        // Sprawdź wszystkie pola z EventSearchDto, np.
                        if (query.OrganisationId is not null)
                        {
                            // Sprawdź, czy Event należy do query.OrganisationId.
                            if (e.OrganisationId != query.OrganisationId) matchFound = false;
                        }

                        if (query.TitleOrDescription is not null)
                        {
                            var TitleMatch = guhf.SearchString(e.Title,       query.TitleOrDescription);
                            var DescMatch  = guhf.SearchString(e.Description, query.TitleOrDescription);
                            if (!TitleMatch && !DescMatch) matchFound = false;
                        }


                        // Zakres dat do wyszukiwania
                        if (query.EventDateFrom is not null)
                        {
                            if (e.EventDate < query.EventDateFrom) matchFound = false;

                        }
                        if (query.EventDateTo is not null)
                        {
                            if (e.EventDate > query.EventDateTo) matchFound = false;
                        }
                            
                       
                        // ...

                        // Jeśli Event jest tym, czego szuka użytkownik,
                        // dodaj go do listy SearchResults.
                        //
                        // Uwaga! Zanim to zrobisz, sprawdź, czy użytkownik
                        // jest twórcą danego wydarzenia! Jeżeli nim nie jest,
                        // wyzeruj EventRegistrations!
                        //if (org is null || e.OrganisationId != org.OrganisationId)
                        //{
                        //    e.EventRegistrations.Clear();
                        //}

                        if (matchFound) SearchResults.Add(e);
                    }

                    return Results.Ok(SearchResults);
                });

            // POST /events/1/add_skill
            group.MapPost("/{id}/add_skill/",
                async (int id, SingleSkillDto dto, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
                {
                    Event? Eve = await dbContext.Events.FindAsync(id);

                    if (Eve is null) return Results.Json(new { message = "Event not found" }, statusCode: 404);

                    // Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której.
                    Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
                    Organisation? org = await guhf.GetOrganisationFromToken(token);

                    // Jeśli token należy do organizacji, która utworzyła to wydarzenie,
                    // to zwróć także EventRegistrations. W przeciwnym razie usuń to pole
                    // przed jego wysłaniem!
                    if (org is null || org.OrganisationId != Eve.OrganisationId) return Results.Unauthorized();

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

                    // Sprawdzamy, czy to wydarzenie nie ma już takiego skilla. Jeżeli ma, nie ma sensu dodawać go kilkukrotnie.
                    EventSkill? es = await dbContext.EventSkills.FirstOrDefaultAsync(e => e.EventId == id && e.SkillId == dto.Skill);
                    if (es is null)
                    {
                        // Nie ma - zatem musimy dodać nowy EventSkill do bazy
                        EventSkill newEs = dto.ToEventSkillEntity(Eve.EventId);
                        dbContext.EventSkills.Add(newEs);
                        await dbContext.SaveChangesAsync();

                    }
                    else
                    {
                        // Ma - (ta para EventId <-> SkillId już istnieje w bazie); ten Event posiada już ten skill
                        return Results.Json(new { message = "Skill already assinged to this event!" }, statusCode: 400);
                    }

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

                });

            // POST /events/1/renive_skill
            group.MapPost("/{id}/remove_skill/",
                async (int id, SingleSkillDto dto, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
                {
                    Event? Eve = await dbContext.Events.FindAsync(id);

                    if (Eve is null) return Results.Json(new { message = "Event not found" }, statusCode: 404);

                    // Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której.
                    Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
                    Organisation? org = await guhf.GetOrganisationFromToken(token);

                    // Jeśli token należy do organizacji, która utworzyła to wydarzenie,
                    // to zwróć także EventRegistrations. W przeciwnym razie usuń to pole
                    // przed jego wysłaniem!
                    if (org is null || org.OrganisationId != Eve.OrganisationId) return Results.Unauthorized();

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

                    // Sprawdzamy, czy to wydarzenie nie ma już takiego skilla. Jeżeli nie ma, to nie ma sensu kasować czegoś, czego nie ma.
                    EventSkill? es = await dbContext.EventSkills.FirstOrDefaultAsync(e => e.EventId == id && e.SkillId == dto.Skill);
                    if (es is not null)
                    {
                        // Ma - zatem musimy usunąć ten EventSkill z bazy
                        await dbContext.EventSkills.Where(e => e.SkillId == dto.Skill)
                            .ExecuteDeleteAsync();
                    }
                    else
                    {
                        // Nie ma - (ta para EventId <-> SkillId nie istnieje w bazie); ten Event nie posiada tego skill'a
                        return Results.Json(new { message = "This skill isn't assinged to this event!" }, statusCode: 400);
                    }

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

            return group;
        }
    }
}
