using MktXService.Domain.Exceptions;
using MktXService.Domain.SeedWork;

namespace MktXService.Domain.AggregatesModel.AutomationFlowAggregate;

/// <summary>
/// EN: AutomationFlow aggregate root for visual workflow automation with triggers and actions.
/// VI: Aggregate root AutomationFlow cho tự động hóa workflow trực quan với triggers và actions.
/// </summary>
public class AutomationFlow : Entity, IAggregateRoot
{
    private Guid _merchantId;
    private string _name = null!;
    private FlowStatus _status = null!;
    private FlowTrigger _trigger = null!;
    private List<FlowNode> _nodes = new();
    private List<FlowConnection> _connections = new();
    private DateTime _createdAt;
    private DateTime? _updatedAt;

    public Guid MerchantId => _merchantId;
    public string Name => _name;
    public FlowStatus Status => _status;
    public int StatusId { get; private set; }
    public FlowTrigger Trigger => _trigger;
    public IReadOnlyList<FlowNode> Nodes => _nodes.AsReadOnly();
    public IReadOnlyList<FlowConnection> Connections => _connections.AsReadOnly();
    public DateTime CreatedAt => _createdAt;
    public DateTime? UpdatedAt => _updatedAt;

    protected AutomationFlow() { }

    public AutomationFlow(Guid merchantId, string name, FlowTrigger trigger) : this()
    {
        if (merchantId == Guid.Empty)
            throw new AutomationFlowDomainException("Merchant ID cannot be empty");
        if (string.IsNullOrWhiteSpace(name))
            throw new AutomationFlowDomainException("Flow name cannot be empty");
        if (trigger == null)
            throw new AutomationFlowDomainException("Flow must have a trigger");

        Id = Guid.NewGuid();
        _merchantId = merchantId;
        _name = name;
        _trigger = trigger;
        _status = FlowStatus.Draft;
        StatusId = FlowStatus.Draft.Id;
        _createdAt = DateTime.UtcNow;
    }

    public void AddNode(FlowNode node)
    {
        if (_status == FlowStatus.Active)
            throw new AutomationFlowDomainException("Cannot modify active flow");

        _nodes.Add(node);
        _updatedAt = DateTime.UtcNow;
    }

    public void RemoveNode(Guid nodeId)
    {
        if (_status == FlowStatus.Active)
            throw new AutomationFlowDomainException("Cannot modify active flow");

        var node = _nodes.FirstOrDefault(n => n.Id == nodeId);
        if (node != null)
        {
            _nodes.Remove(node);
            _connections.RemoveAll(c => c.FromNodeId == nodeId || c.ToNodeId == nodeId);
            _updatedAt = DateTime.UtcNow;
        }
    }

    public void Connect(Guid fromNodeId, Guid toNodeId, string? condition = null)
    {
        if (_status == FlowStatus.Active)
            throw new AutomationFlowDomainException("Cannot modify active flow");

        // Verify nodes exist
        if (!_nodes.Any(n => n.Id == fromNodeId))
            throw new AutomationFlowDomainException("Source node does not exist");
        if (!_nodes.Any(n => n.Id == toNodeId))
            throw new AutomationFlowDomainException("Target node does not exist");

        // Check for circular connection
        if (fromNodeId == toNodeId)
            throw new AutomationFlowDomainException("Cannot connect node to itself");

        var connection = new FlowConnection(Id, fromNodeId, toNodeId, condition);
        _connections.Add(connection);
        _updatedAt = DateTime.UtcNow;
    }

    public void Activate()
    {
        if (_status == FlowStatus.Active)
            throw new AutomationFlowDomainException("Flow is already active");

        if (_nodes.Count == 0)
            throw new AutomationFlowDomainException("Flow must have at least one node");

        _status = FlowStatus.Active;
        StatusId = FlowStatus.Active.Id;
        _updatedAt = DateTime.UtcNow;
    }

    public void Deactivate()
    {
        if (_status != FlowStatus.Active)
            throw new AutomationFlowDomainException("Only active flows can be deactivated");

        _status = FlowStatus.Inactive;
        StatusId = FlowStatus.Inactive.Id;
        _updatedAt = DateTime.UtcNow;
    }

    public void UpdateName(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new AutomationFlowDomainException("Flow name cannot be empty");

        _name = name;
        _updatedAt = DateTime.UtcNow;
    }
}

public class FlowStatus : Enumeration
{
    public static FlowStatus Draft = new(1, nameof(Draft));
    public static FlowStatus Active = new(2, nameof(Active));
    public static FlowStatus Inactive = new(3, nameof(Inactive));

    public FlowStatus(int id, string name) : base(id, name) { }
    public static IEnumerable<FlowStatus> List() => GetAll<FlowStatus>();
    public static FlowStatus From(int id) => List().Single(s => s.Id == id);
}

public record FlowTrigger(string Type, Dictionary<string, object>? Config);

public class FlowNode : Entity
{
    public Guid FlowId { get; private set; }
    public string Type { get; private set; } = null!;
    public Dictionary<string, object>? Config { get; private set; }
    public int? PositionX { get; private set; }
    public int? PositionY { get; private set; }

    protected FlowNode() { }

    public FlowNode(Guid flowId, string type, Dictionary<string, object>? config = null, int? posX = null, int? posY = null)
    {
        Id = Guid.NewGuid();
        FlowId = flowId;
        Type = type;
        Config = config;
        PositionX = posX;
        PositionY = posY;
    }
}

public class FlowConnection : Entity
{
    public Guid FlowId { get; private set; }
    public Guid FromNodeId { get; private set; }
    public Guid ToNodeId { get; private set; }
    public string? Condition { get; private set; }

    protected FlowConnection() { }

    public FlowConnection(Guid flowId, Guid fromNodeId, Guid toNodeId, string? condition = null)
    {
        Id = Guid.NewGuid();
        FlowId = flowId;
        FromNodeId = fromNodeId;
        ToNodeId = toNodeId;
        Condition = condition;
    }
}
