using ColaFlow.Modules.Mcp.Application.Services;
using ColaFlow.Modules.Mcp.Domain.Events;
using ColaFlow.Modules.Mcp.Domain.ValueObjects;
using ColaFlow.Modules.ProjectManagement.Application.Commands.CreateEpic;
using ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject;
using ColaFlow.Modules.ProjectManagement.Application.Commands.CreateStory;
using ColaFlow.Modules.ProjectManagement.Application.Commands.CreateTask;
using ColaFlow.Modules.ProjectManagement.Application.Commands.UpdateEpic;
using ColaFlow.Modules.ProjectManagement.Application.Commands.UpdateProject;
using ColaFlow.Modules.ProjectManagement.Application.Commands.UpdateStory;
using ColaFlow.Modules.ProjectManagement.Application.Commands.UpdateTask;
using MediatR;
using Microsoft.Extensions.Logging;
using System.Text.Json;

namespace ColaFlow.Modules.Mcp.Application.EventHandlers;

/// <summary>
/// Event handler for PendingChangeApprovedEvent
/// Executes the approved change by dispatching appropriate commands
/// </summary>
public class PendingChangeApprovedEventHandler : INotificationHandler<PendingChangeApprovedEvent>
{
    private readonly IMediator _mediator;
    private readonly IPendingChangeService _pendingChangeService;
    private readonly ILogger<PendingChangeApprovedEventHandler> _logger;

    public PendingChangeApprovedEventHandler(
        IMediator mediator,
        IPendingChangeService pendingChangeService,
        ILogger<PendingChangeApprovedEventHandler> logger)
    {
        _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
        _pendingChangeService = pendingChangeService ?? throw new ArgumentNullException(nameof(pendingChangeService));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task Handle(PendingChangeApprovedEvent notification, CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Handling PendingChangeApprovedEvent - PendingChangeId={PendingChangeId}, EntityType={EntityType}, Operation={Operation}",
            notification.PendingChangeId, notification.Diff.EntityType, notification.Diff.Operation);

        try
        {
            // Execute the change based on entity type and operation
            var result = await ExecuteChangeAsync(notification.Diff, cancellationToken);

            // Mark as applied
            await _pendingChangeService.MarkAsAppliedAsync(
                notification.PendingChangeId,
                result,
                cancellationToken);

            _logger.LogInformation(
                "PendingChange executed successfully - PendingChangeId={PendingChangeId}, Result={Result}",
                notification.PendingChangeId, result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "Failed to execute PendingChange - PendingChangeId={PendingChangeId}",
                notification.PendingChangeId);

            // Mark as failed (store error in ApplicationResult)
            await _pendingChangeService.MarkAsAppliedAsync(
                notification.PendingChangeId,
                $"Failed: {ex.Message}",
                cancellationToken);
        }
    }

    private async Task<string> ExecuteChangeAsync(DiffPreview diff, CancellationToken cancellationToken)
    {
        var operation = diff.Operation.ToLowerInvariant();
        var entityType = diff.EntityType.ToLowerInvariant();

        _logger.LogDebug(
            "Executing {Operation} on {EntityType}",
            operation, entityType);

        return (operation, entityType) switch
        {
            ("create", "project") => await ExecuteCreateProjectAsync(diff, cancellationToken),
            ("update", "project") => await ExecuteUpdateProjectAsync(diff, cancellationToken),
            ("create", "epic") => await ExecuteCreateEpicAsync(diff, cancellationToken),
            ("update", "epic") => await ExecuteUpdateEpicAsync(diff, cancellationToken),
            ("create", "story") => await ExecuteCreateStoryAsync(diff, cancellationToken),
            ("update", "story") => await ExecuteUpdateStoryAsync(diff, cancellationToken),
            ("create", "task") => await ExecuteCreateTaskAsync(diff, cancellationToken),
            ("update", "task") => await ExecuteUpdateTaskAsync(diff, cancellationToken),
            _ => throw new NotSupportedException($"Operation '{operation}' on entity type '{entityType}' is not supported")
        };
    }

    #region Project Operations

    private async Task<string> ExecuteCreateProjectAsync(DiffPreview diff, CancellationToken cancellationToken)
    {
        var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(diff.AfterData ?? "{}");
        if (data == null) throw new InvalidOperationException("Invalid AfterData for CreateProject");

        var command = new CreateProjectCommand
        {
            Name = GetStringValue(data, "name", "New Project"),
            Description = GetStringValue(data, "description", ""),
            Key = GetStringValue(data, "key", "PROJ"),
            OwnerId = GetGuidValue(data, "ownerId")
        };

        var result = await _mediator.Send(command, cancellationToken);
        return $"Project created: {result.Id} - {result.Name}";
    }

    private async Task<string> ExecuteUpdateProjectAsync(DiffPreview diff, CancellationToken cancellationToken)
    {
        var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(diff.AfterData ?? "{}");
        if (data == null) throw new InvalidOperationException("Invalid AfterData for UpdateProject");

        var command = new UpdateProjectCommand
        {
            ProjectId = diff.EntityId ?? throw new InvalidOperationException("EntityId is required for Update"),
            Name = GetStringValue(data, "name"),
            Description = GetStringValue(data, "description"),
        };

        var result = await _mediator.Send(command, cancellationToken);
        return $"Project updated: {result.Id} - {result.Name}";
    }

    #endregion

    #region Epic Operations

    private async Task<string> ExecuteCreateEpicAsync(DiffPreview diff, CancellationToken cancellationToken)
    {
        var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(diff.AfterData ?? "{}");
        if (data == null) throw new InvalidOperationException("Invalid AfterData for CreateEpic");

        var command = new CreateEpicCommand
        {
            ProjectId = GetGuidValue(data, "projectId"),
            Name = GetStringValue(data, "name", "New Epic"),
            Description = GetStringValue(data, "description", ""),
            CreatedBy = GetGuidValue(data, "createdBy")
        };

        var result = await _mediator.Send(command, cancellationToken);
        return $"Epic created: {result.Id} - {result.Name}";
    }

    private async Task<string> ExecuteUpdateEpicAsync(DiffPreview diff, CancellationToken cancellationToken)
    {
        var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(diff.AfterData ?? "{}");
        if (data == null) throw new InvalidOperationException("Invalid AfterData for UpdateEpic");

        var command = new UpdateEpicCommand
        {
            EpicId = diff.EntityId ?? throw new InvalidOperationException("EntityId is required for Update"),
            Name = GetStringValue(data, "name"),
            Description = GetStringValue(data, "description")
        };

        var result = await _mediator.Send(command, cancellationToken);
        return $"Epic updated: {result.Id} - {result.Name}";
    }

    #endregion

    #region Story Operations

    private async Task<string> ExecuteCreateStoryAsync(DiffPreview diff, CancellationToken cancellationToken)
    {
        var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(diff.AfterData ?? "{}");
        if (data == null) throw new InvalidOperationException("Invalid AfterData for CreateStory");

        var command = new CreateStoryCommand
        {
            EpicId = GetGuidValue(data, "epicId"),
            Title = GetStringValue(data, "title", "New Story"),
            Description = GetStringValue(data, "description", ""),
            Priority = GetStringValue(data, "priority", "Medium"),
            AssigneeId = GetNullableGuidValue(data, "assigneeId"),
            EstimatedHours = GetNullableDecimalValue(data, "estimatedHours"),
            CreatedBy = GetGuidValue(data, "createdBy")
        };

        var result = await _mediator.Send(command, cancellationToken);
        return $"Story created: {result.Id} - {result.Title}";
    }

    private async Task<string> ExecuteUpdateStoryAsync(DiffPreview diff, CancellationToken cancellationToken)
    {
        var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(diff.AfterData ?? "{}");
        if (data == null) throw new InvalidOperationException("Invalid AfterData for UpdateStory");

        var command = new UpdateStoryCommand
        {
            StoryId = diff.EntityId ?? throw new InvalidOperationException("EntityId is required for Update"),
            Title = GetStringValue(data, "title"),
            Description = GetStringValue(data, "description"),
            Status = GetStringValue(data, "status"),
            Priority = GetStringValue(data, "priority")
        };

        var result = await _mediator.Send(command, cancellationToken);
        return $"Story updated: {result.Id} - {result.Title}";
    }

    #endregion

    #region Task Operations

    private async Task<string> ExecuteCreateTaskAsync(DiffPreview diff, CancellationToken cancellationToken)
    {
        var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(diff.AfterData ?? "{}");
        if (data == null) throw new InvalidOperationException("Invalid AfterData for CreateTask");

        var command = new CreateTaskCommand
        {
            StoryId = GetGuidValue(data, "storyId"),
            Title = GetStringValue(data, "title", "New Task"),
            Description = GetStringValue(data, "description", ""),
            Priority = GetStringValue(data, "priority", "Medium"),
            EstimatedHours = GetNullableDecimalValue(data, "estimatedHours"),
            AssigneeId = GetNullableGuidValue(data, "assigneeId"),
            CreatedBy = GetGuidValue(data, "createdBy")
        };

        var result = await _mediator.Send(command, cancellationToken);
        return $"Task created: {result.Id} - {result.Title}";
    }

    private async Task<string> ExecuteUpdateTaskAsync(DiffPreview diff, CancellationToken cancellationToken)
    {
        var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(diff.AfterData ?? "{}");
        if (data == null) throw new InvalidOperationException("Invalid AfterData for UpdateTask");

        var command = new UpdateTaskCommand
        {
            TaskId = diff.EntityId ?? throw new InvalidOperationException("EntityId is required for Update"),
            Title = GetStringValue(data, "title"),
            Description = GetStringValue(data, "description"),
            Status = GetStringValue(data, "status"),
            Priority = GetStringValue(data, "priority")
        };

        var result = await _mediator.Send(command, cancellationToken);
        return $"Task updated: {result.Id} - {result.Title}";
    }

    #endregion

    #region Helper Methods

    private static string GetStringValue(Dictionary<string, JsonElement> data, string key, string? defaultValue = null)
    {
        if (data.TryGetValue(key, out var element) && element.ValueKind == JsonValueKind.String)
        {
            return element.GetString() ?? defaultValue ?? string.Empty;
        }
        return defaultValue ?? string.Empty;
    }

    private static Guid GetGuidValue(Dictionary<string, JsonElement> data, string key)
    {
        if (data.TryGetValue(key, out var element))
        {
            if (element.ValueKind == JsonValueKind.String)
            {
                var stringValue = element.GetString();
                if (Guid.TryParse(stringValue, out var guid))
                {
                    return guid;
                }
            }
        }
        throw new InvalidOperationException($"Required Guid field '{key}' is missing or invalid");
    }

    private static Guid? GetNullableGuidValue(Dictionary<string, JsonElement> data, string key)
    {
        if (data.TryGetValue(key, out var element))
        {
            if (element.ValueKind == JsonValueKind.String)
            {
                var stringValue = element.GetString();
                if (!string.IsNullOrWhiteSpace(stringValue) && Guid.TryParse(stringValue, out var guid))
                {
                    return guid;
                }
            }
        }
        return null;
    }

    private static decimal? GetNullableDecimalValue(Dictionary<string, JsonElement> data, string key)
    {
        if (data.TryGetValue(key, out var element))
        {
            if (element.ValueKind == JsonValueKind.Number)
            {
                return element.GetDecimal();
            }
            if (element.ValueKind == JsonValueKind.String)
            {
                var stringValue = element.GetString();
                if (decimal.TryParse(stringValue, out var decimalValue))
                {
                    return decimalValue;
                }
            }
        }
        return null;
    }

    #endregion
}
