using System.Linq.Expressions;
using e_suite.API.Common;
using e_suite.API.Common.exceptions;
using e_suite.API.Common.models;
using e_suite.API.Common.repository;
using e_suite.Database.Audit;
using e_suite.Database.Core.Extensions;
using e_suite.Database.Core.Models;
using e_suite.Database.Core.Tables.Printer;
using e_suite.Messaging.Common;
using e_suite.Utilities.Pagination;
using eSuite.Core.Miscellaneous;
using Microsoft.EntityFrameworkCore;

namespace e_suite.Modules.SiteManager;

public class SiteManager : ISiteManager
{
    private readonly ISiteManagerRepository _siteManagerRepository;
    private readonly IOrganisationsManagerRepository _organisationManagerRepository;
    private readonly IEFlowSyncMessageSender _eFlowSyncMessageSender;

    public SiteManager(ISiteManagerRepository siteManagerRepository, IOrganisationsManagerRepository organisationManagerRepository, IEFlowSyncMessageSender eFlowSyncMessageSender)
    {
        _siteManagerRepository = siteManagerRepository;
        _organisationManagerRepository = organisationManagerRepository;
        _eFlowSyncMessageSender = eFlowSyncMessageSender;
    }

    private static void Assert<T>(T? item, string message = "Site not found") where T : ISoftDeletable
    {
        if (item == null || item.Deleted)
        {
            throw new NotFoundException(message);
        }
    }

    public async Task<IPaginatedData<ReadSite>> GetSites(Paging paging, CancellationToken cancellationToken)
    {
        var forms = _siteManagerRepository.GetSites();


        var paginatedData = await PaginatedData.Paginate(forms, paging,
            KeySelector, cancellationToken);

        var paginatedResult = new PaginatedData<ReadSite>
        {
            Count = paginatedData.Count,
            Page = paginatedData.Page,
            PageSize = paginatedData.PageSize,
            Data = paginatedData.Data.Select(MapSite)
        };
        return paginatedResult;
    }

    private Expression<Func<Site, object>> KeySelector(string? sortKey)
    {
        return sortKey?.ToLowerInvariant() switch
        {
            "id" => x => x.Id,
            "guid" => x => x.Guid,
            "organisationid" => x => x.OrganisationId,
            _ => x => x.Name
        };
    }

    private static ReadSite MapSite(Site site)
    {
        return new ReadSite
        { 
            Id = site.Id,
            Guid = site.Guid,
            Name = site.Name,
            Address = site.Address,
            Status = site.Status,
            SigmaId = site.SigmaId,
            OrganisationId = new GeneralIdRef
            {
                Id = site.Organisation.Id,
                Guid = site.Organisation.Guid
            }
        };
    }

    public async Task<ReadSite> GetSite(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        var site = await _siteManagerRepository.GetSite(generalIdRef, cancellationToken);
        Assert(site);
        return MapSite(site!);
    }

    public async Task<ReadSite> GetSiteBySigmaId(long sigmaId, CancellationToken cancellationToken)
    {
        var site = await _siteManagerRepository.GetSite(sigmaId, cancellationToken);
        Assert(site);
        return MapSite(site!);
    }


    public async Task<ReadSite> GetSiteByName(IGeneralIdRef organisationGeneralIdRef, string siteName, CancellationToken cancellationToken)
    {
        var organisation = await _organisationManagerRepository.FindOrganisation(organisationGeneralIdRef, cancellationToken) ?? throw new ArgumentException("Unable to find organisation", nameof(organisationGeneralIdRef));
        var site = await _siteManagerRepository.GetSites().Where( x => x.Organisation == organisation && x.Name.Equals(siteName)).SingleOrDefaultAsync(cancellationToken) ?? throw new NotFoundException("Unable to find site by name");
        Assert(site);
        return MapSite(site!);
    }

    public async Task CreateSite(AuditUserDetails auditUserDetails, CreateSite site,
        bool triggerEFlowSync, CancellationToken cancellationToken)
    {
        await CreateSites(auditUserDetails, [site], triggerEFlowSync, cancellationToken);
    }

    public async Task CreateSites(AuditUserDetails auditUserDetails, IEnumerable<CreateSite> sites, bool triggerEFlowSync, CancellationToken cancellationToken)
    {
        await _siteManagerRepository.TransactionAsync(async () =>
        {
            var newSites = new List<Site>();

            foreach (var site in sites)
            {
                if (site.Guid != null &&
                    await _siteManagerRepository.GetSite(new GeneralIdRef { Guid = site.Guid }, cancellationToken) !=
                    null)
                    throw new ExistsException("A site with this guid already exists");

                var organisation =
                    await _organisationManagerRepository.FindOrganisation(site.OrganisationId, cancellationToken);
                Assert(organisation, "Organisation not found");

                if (await _siteManagerRepository.GetSites()
                        .FirstOrDefaultAsync(x => x.Name == site.Name && x.OrganisationId == organisation!.Id,
                            cancellationToken) != null)
                    throw new ExistsException("A site with this name already exists");

                var newSite = new Site
                {
                    Name = site.Name,
                    Address = site.Address,
                    Status = site.Status,
                    Organisation = organisation!,
                    OrganisationId = organisation!.Id,
                    SigmaId = site.SigmaId,
                    Guid = site.Guid ?? Guid.NewGuid()
                };

                newSites.Add(newSite);
            }

            await _siteManagerRepository.CreateSite(auditUserDetails, newSites, cancellationToken);
            if (triggerEFlowSync)
                _eFlowSyncMessageSender.SyncEFlowPrinterCategories();
        });
    }

    public async Task EditSite(AuditUserDetails auditUserDetails, EditSite site, bool triggerEFlowSync, CancellationToken cancellationToken)
    {
        await _siteManagerRepository.TransactionAsync(async () =>
        {
            var siteOriginal = await _siteManagerRepository.GetSite(site.Id, cancellationToken);
            Assert(siteOriginal);
            siteOriginal!.Name = site.Name;
            siteOriginal!.Address = site.Address;
            siteOriginal!.Status = site.Status;
            siteOriginal!.SigmaId = site.SigmaId;

            if (!siteOriginal.Organisation.IsMatch(site.OrganisationId))
                throw new InvalidOperationException("Not allow to move a site from one organisation to another");

            await _siteManagerRepository.EditSite(auditUserDetails, siteOriginal, cancellationToken);
            if (triggerEFlowSync)
                _eFlowSyncMessageSender.SyncEFlowPrinterCategories();
        });
    }

    public async Task DeleteSite(AuditUserDetails auditUserDetails, IGeneralIdRef generalIdRef, bool triggerEFlowSync, CancellationToken cancellationToken)
    {
        await _siteManagerRepository.TransactionAsync(async () =>
        {
            var formTemplate = await _siteManagerRepository.GetSite(generalIdRef, cancellationToken) ??
                throw new NotFoundException("Site not found");

            if (formTemplate.Deleted)
                throw new InvalidOperationException("Site already deleted");

            formTemplate.Deleted = true;
            await _siteManagerRepository.EditSite(auditUserDetails, formTemplate, cancellationToken);
            if (triggerEFlowSync)
                _eFlowSyncMessageSender.SyncEFlowPrinterCategories();
        });
    }
}