using System.ComponentModel;
using e_suite.API.Common;
using e_suite.API.Common.exceptions;
using e_suite.API.Common.repository;
using e_suite.Database.Audit;
using e_suite.Database.Core.Extensions;
using e_suite.Utilities.Pagination;
using e_suite.Workflow.Core.Attributes;
using e_suite.Workflow.Core.Extensions;
using e_suite.Workflow.Core.Interfaces;
using eSuite.Core.Miscellaneous;
using System.Linq.Expressions;
using System.Reflection;
using e_suite.Database.Core.Tables.Workflow;

//using WorkflowVersion = e_suite.Workflow.Core.WorkflowVersion;

namespace e_suite.Modules.WorkflowTemplatesManager;

public class WorkflowTemplateManager : IWorkflowTemplateManager
{
    private readonly IWorkflowTemplateRepository _workflowTemplateRepository;
    private readonly IWorkflowConverter _workflowConverter;
    private readonly IDomainRepository _domainRepository;
    private readonly IPatchFactory _patchFactory;

    public WorkflowTemplateManager(IWorkflowTemplateRepository workflowTemplateRepository, IPatchFactory patchFactory, IDomainRepository domainRepository, IWorkflowConverter workflowConverter)
    {
        _workflowTemplateRepository = workflowTemplateRepository;
        _patchFactory = patchFactory;
        _domainRepository = domainRepository;
        _workflowConverter = workflowConverter;
    }

    public async Task<PaginatedData<GetWorkflowTemplate>> GetWorkflowTemplatesAsync(Paging paging, CancellationToken cancellationToken)
    {
        var ssoProviders = _workflowTemplateRepository.GetWorkflows().Where(x => x.Deleted == false);

        var paginatedData = await PaginatedData.Paginate<e_suite.Database.Core.Tables.Workflow.Workflow, GetWorkflowTemplate>(ssoProviders, paging,
            WorkflowKeySelector, cancellationToken);

        return paginatedData;
    }

    private Expression<Func<e_suite.Database.Core.Tables.Workflow.Workflow, object>> WorkflowKeySelector(string sortKey)
    {
        return sortKey?.ToLowerInvariant() switch
        {
            "id" => x => x.Id,
            "guid" => x => x.Guid,
            "name" => x => x.Name,
            "lastupdated" => x => x.LastUpdated,
            _ => x => x.Id
        };
    }

    public async Task<PaginatedData<GetWorkflowTemplateVersion>> GetWorkflowTemplateVersionsAsync(Paging paging, CancellationToken cancellationToken)
    {
        var ssoProviders = _workflowTemplateRepository.GetWorkflowVersions().Where(x => x.Deleted == false);

        var paginatedData = await PaginatedData.Paginate<e_suite.Database.Core.Tables.Workflow.WorkflowVersion, GetWorkflowTemplateVersion>(ssoProviders, paging,
            KeySelector, cancellationToken);

        return paginatedData;
    }
    
    private Expression<Func<e_suite.Database.Core.Tables.Workflow.WorkflowVersion, object>> KeySelector(string sortKey)
    {
        return sortKey?.ToLowerInvariant() switch
        {
            "id" => x => x.Id,
            "activitynametemplate" => x => x.ActivityNameTemplate,
            "description" => x => x.Description,
            "domain.name" => x => x.Domain.Name,
            "workflow.name" => x => x.Workflow.Name,
            _ => x => x.Id
        };
    }

    public async Task<GetWorkflowTemplateVersion?> GetWorkflowTemplateAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        var workflow = await _workflowTemplateRepository.GetWorkflows().FindByGeneralIdRefAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("Unable to find Workflow");

        var dbWorkflowTemplate = _workflowTemplateRepository.GetWorkflowVersions().Where(x => x.WorkflowId == workflow.Id).OrderByDescending( x => x.Version).First();

        return new GetWorkflowTemplateVersion(dbWorkflowTemplate);
    }

    public async Task EditTemplateVersion(
        AuditUserDetails auditUserDetails,
        GetWorkflowTemplateVersion template,
        CancellationToken cancellationToken
    )
    {
        await AlterWorkflowTemplateVersionAsync(auditUserDetails, template.ToGeneralIdRef()!, async version =>
        {
            var domain = await _domainRepository.GetDomainById(template.DomainId!, cancellationToken) ?? throw new NotFoundException("Unable to find Domain with provided id");

            var workflow = version.Workflow;
            workflow.Name = template.WorkflowName;

            var newVersion = new e_suite.Database.Core.Tables.Workflow.WorkflowVersion
            {
                Domain = domain,
                DomainId = domain.Id,
                Description = template.Description,
                ActivityNameTemplate = template.ActivityNameTemplate,
                Deleted = false,
                Version = template.Version + 1,
                Tasks = template.Tasks,
                Workflow = workflow
            };
            return newVersion;
        }, cancellationToken);
    }

    public async Task PatchTemplateVersion(
        AuditUserDetails auditUserDetails,
        IGeneralIdRef templateId,
        PatchWorkflowTemplateVersion patchTemplate,
        CancellationToken cancellationToken
    )
    {
        var patch = _patchFactory.Create(patchTemplate);
        await AlterWorkflowTemplateVersionAsync(auditUserDetails, templateId, async version =>
        {
            await patch.ApplyTo(version);
            return version;
        }, cancellationToken);
    }

    public async Task PostTemplateVersion(
        AuditUserDetails auditUserDetails,
        CreateWorkflowTemplateVersion template,
        CancellationToken cancellationToken
    )
    {
        await _workflowTemplateRepository.TransactionAsync(async () =>
            {
                if (_workflowTemplateRepository.GetWorkflows().FirstOrDefault(x => x.Name == template.WorkflowName) !=
                    null)
                {
                    throw new ExistsException("Workflow already exists");
                }

                var workflow = new Database.Core.Tables.Workflow.Workflow
                {
                    Name = template.WorkflowName,
                    Guid = Guid.NewGuid()
                };

                await _workflowTemplateRepository.AddWorkflow(auditUserDetails, workflow, cancellationToken);

                var workflowDomain = await _domainRepository.GetDomainById(template.DomainId, cancellationToken) ?? throw new NotFoundException("Unable to find domain");

                var dbWorkflowTemplate = new WorkflowVersion
                {
                    ActivityNameTemplate = template.ActivityNameTemplate,
                    Description = template.Description,
                    Domain = workflowDomain,
                    Tasks = template.Tasks,
                    Workflow = workflow,
                };

                //This will attempt to parse the data onto the internal workflow structure.
                Workflow.Core.WorkflowVersion? workflowTemplate = _workflowConverter.DeserialiseFromDatabase(dbWorkflowTemplate);
                if (workflowTemplate is null)
                {
                    throw new InvalidDataException("The workflow template is not valid");
                }

                await _workflowTemplateRepository.AddWorkflowVersion(auditUserDetails, dbWorkflowTemplate, cancellationToken);
            }
        );
    }

    public async Task DeleteTemplateVersion(AuditUserDetails auditUserDetails, IGeneralIdRef templateId, CancellationToken cancellationToken)
    {
        await AlterWorkflowTemplateVersionAsync(auditUserDetails, templateId, async version =>
        {
            version.Deleted = true;
            return version;
        }, cancellationToken);
    }

    private async Task AlterWorkflowTemplateVersionAsync(
        AuditUserDetails auditUserDetails,
        IGeneralIdRef generalIdRef,
        Func<e_suite.Database.Core.Tables.Workflow.WorkflowVersion,
            Task<e_suite.Database.Core.Tables.Workflow.WorkflowVersion?>> applyChanges,
        CancellationToken cancellationToken
    )
    {
        await _workflowTemplateRepository.TransactionAsync(async () =>
        {
            var workflowVersion =
                await _workflowTemplateRepository.GetWorkflowVersions()
                    .FindByGeneralIdRefAsync(generalIdRef, cancellationToken) ??
                throw new NotFoundException("WorkflowVersion with this id does not exists");

            var newVersion = await applyChanges(workflowVersion);
            if (newVersion != workflowVersion)
                await _workflowTemplateRepository.AddWorkflowVersion(auditUserDetails, newVersion, cancellationToken);
            else
                await _workflowTemplateRepository.EditWorkflowVersionAsync(auditUserDetails, workflowVersion,
                    cancellationToken);
        });
    }

    private static string FormatInterfaceName(Type interfaceType)
    {
        if (!interfaceType.IsGenericType)
            return interfaceType.Name;

        var genericName = interfaceType.Name;
        var tickIndex = genericName.IndexOf('`');
        if (tickIndex > 0)
            genericName = genericName.Substring(0, tickIndex);

        var genericArgs = interfaceType.GetGenericArguments();
        var formattedArgs = string.Join(", ", genericArgs.Select(a => a.Name));

        return $"{genericName}<{formattedArgs}>";
    }

    public Task<List<TaskMetadata>> GetAllowedTaskMetadataList(string? taskType, CancellationToken cancellationToken)
    {
        var taskTypeAttribute = StageExtensions.GetTaskAttributeType(taskType);

        var allowedTypes = StageExtensions.GetAllowedTaskTypes(taskTypeAttribute);

        var result = allowedTypes
            .Select(t =>
            {
                var interfaces = t.GetInterfaces();
                var capabilities = interfaces
                    .Where(i => i.GetCustomAttribute<TaskCapabilityAttribute>() != null)
                    .Select(FormatInterfaceName)
                    .ToList();

                var taskAttribute = t.GetCustomAttributes(typeof(TaskTypeAttribute), inherit: true).FirstOrDefault() as TaskTypeAttribute;

                var newTaskMetadata = new TaskMetadata
                {
                    TaskType = t.FullName!,
                    DisplayName = t.Name,
                    Icon = taskAttribute?.TaskIcon ?? string.Empty
                };

                newTaskMetadata.Capabilities.AddRange(capabilities);

                var outcomeInterface = interfaces
                    .FirstOrDefault(i => i.IsGenericType &&
                                         i.GetGenericTypeDefinition() == typeof(IOutcome<>));
                var outcomeEnumType = outcomeInterface.GetGenericArguments()[0];

                newTaskMetadata.OutcomeLabel = outcomeEnumType
                    .GetCustomAttribute<DescriptionAttribute>()?
                    .Description ?? outcomeEnumType.Name;

                newTaskMetadata.Outcomes = Enum.GetNames(outcomeEnumType).Select( x => $"{outcomeEnumType.Name}.{x}").ToList();

                return newTaskMetadata;
            })
            .ToList();

        return Task.FromResult(result);
    }

    private async Task AlterWorkflowTemplateAsync(
        AuditUserDetails auditUserDetails,
        IGeneralIdRef generalIdRef,
        Func<e_suite.Database.Core.Tables.Workflow.Workflow,
            Task<e_suite.Database.Core.Tables.Workflow.Workflow?>> applyChanges,
        CancellationToken cancellationToken
    )
    {
        await _workflowTemplateRepository.TransactionAsync(async () =>
        {
            var workflow =
                await _workflowTemplateRepository.GetWorkflows()
                    .FindByGeneralIdRefAsync(generalIdRef, cancellationToken) ??
                throw new NotFoundException("Workflow with this id does not exists");

            var newWorkflow = await applyChanges(workflow);
            if (newWorkflow != workflow)
                await _workflowTemplateRepository.AddWorkflow(auditUserDetails, newWorkflow, cancellationToken);
            else
                await _workflowTemplateRepository.EditWorkflowAsync(auditUserDetails, workflow,
                    cancellationToken);
        });
    }
    public async Task DeleteTemplate(AuditUserDetails auditUserDetails, GeneralIdRef templateId, CancellationToken cancellationToken)
    {
        await AlterWorkflowTemplateAsync(auditUserDetails, templateId, async version =>
        {
            version.Deleted = true;
            return version;
        }, cancellationToken);
    }
}