using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ScadaLink.Commons.Entities.Deployment;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.Commons.Messages.Artifacts;
using ScadaLink.Commons.Types;
using ScadaLink.Communication;

namespace ScadaLink.DeploymentManager;

/// <summary>
/// WP-7: System-wide artifact deployment.
/// Broadcasts artifacts (shared scripts, external systems, notification lists, DB connections,
/// data connections, and SMTP configurations) to all sites with per-site tracking.
///
/// - Successful sites are NOT rolled back on other failures.
/// - Failed sites are retryable individually.
/// - 120s timeout per site.
/// - Cross-site version skew is supported.
/// </summary>
public class ArtifactDeploymentService
{
    private readonly ISiteRepository _siteRepo;
    private readonly IDeploymentManagerRepository _deploymentRepo;
    private readonly ITemplateEngineRepository _templateRepo;
    private readonly IExternalSystemRepository _externalSystemRepo;
    private readonly INotificationRepository _notificationRepo;
    private readonly CommunicationService _communicationService;
    private readonly IAuditService _auditService;
    private readonly DeploymentManagerOptions _options;
    private readonly ILogger<ArtifactDeploymentService> _logger;

    public ArtifactDeploymentService(
        ISiteRepository siteRepo,
        IDeploymentManagerRepository deploymentRepo,
        ITemplateEngineRepository templateRepo,
        IExternalSystemRepository externalSystemRepo,
        INotificationRepository notificationRepo,
        CommunicationService communicationService,
        IAuditService auditService,
        IOptions<DeploymentManagerOptions> options,
        ILogger<ArtifactDeploymentService> logger)
    {
        _siteRepo = siteRepo;
        _deploymentRepo = deploymentRepo;
        _templateRepo = templateRepo;
        _externalSystemRepo = externalSystemRepo;
        _notificationRepo = notificationRepo;
        _communicationService = communicationService;
        _auditService = auditService;
        _options = options.Value;
        _logger = logger;
    }

    /// <summary>
    /// Collects all artifact types from repositories and builds a <see cref="DeployArtifactsCommand"/>
    /// scoped to a specific site's data connections.
    /// </summary>
    public async Task<DeployArtifactsCommand> BuildDeployArtifactsCommandAsync(
        int siteId,
        CancellationToken cancellationToken = default)
    {
        var sharedScripts = await _templateRepo.GetAllSharedScriptsAsync(cancellationToken);
        var externalSystems = await _externalSystemRepo.GetAllExternalSystemsAsync(cancellationToken);
        var dbConnections = await _externalSystemRepo.GetAllDatabaseConnectionsAsync(cancellationToken);
        var notificationLists = await _notificationRepo.GetAllNotificationListsAsync(cancellationToken);
        var dataConnections = await _siteRepo.GetDataConnectionsBySiteIdAsync(siteId, cancellationToken);
        var smtpConfigurations = await _notificationRepo.GetAllSmtpConfigurationsAsync(cancellationToken);

        // Map shared scripts
        var scriptArtifacts = sharedScripts.Select(s =>
            new SharedScriptArtifact(s.Name, s.Code, s.ParameterDefinitions, s.ReturnDefinition)).ToList();

        // Map external systems (serialize methods per system)
        var externalSystemArtifacts = new List<ExternalSystemArtifact>();
        foreach (var es in externalSystems)
        {
            var methods = await _externalSystemRepo.GetMethodsByExternalSystemIdAsync(es.Id, cancellationToken);
            var methodsJson = methods.Count > 0
                ? JsonSerializer.Serialize(methods.Select(m => new
                {
                    m.Name,
                    m.HttpMethod,
                    m.Path,
                    m.ParameterDefinitions,
                    m.ReturnDefinition
                }))
                : null;
            externalSystemArtifacts.Add(new ExternalSystemArtifact(
                es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, methodsJson));
        }

        // Map database connections
        var dbConnectionArtifacts = dbConnections.Select(d =>
            new DatabaseConnectionArtifact(d.Name, d.ConnectionString, d.MaxRetries, d.RetryDelay)).ToList();

        // Map notification lists
        var notificationListArtifacts = notificationLists.Select(nl =>
            new NotificationListArtifact(nl.Name, nl.Recipients.Select(r => r.EmailAddress).ToList())).ToList();

        // Map data connections
        var dataConnectionArtifacts = dataConnections.Select(dc =>
            new DataConnectionArtifact(dc.Name, dc.Protocol, dc.PrimaryConfiguration, dc.BackupConfiguration, dc.FailoverRetryCount)).ToList();

        // Map SMTP configurations — use Host as the artifact name (matches SQLite PK on site)
        var smtpArtifacts = smtpConfigurations.Select(smtp =>
            new SmtpConfigurationArtifact(
                $"{smtp.Host}:{smtp.Port}", smtp.Host, smtp.Port, smtp.AuthType, smtp.FromAddress,
                smtp.Credentials, null, smtp.TlsMode)).ToList();

        return new DeployArtifactsCommand(
            Guid.NewGuid().ToString("N"),
            scriptArtifacts,
            externalSystemArtifacts,
            dbConnectionArtifacts,
            notificationListArtifacts,
            dataConnectionArtifacts,
            smtpArtifacts,
            DateTimeOffset.UtcNow);
    }

    /// <summary>
    /// Deploys artifacts to all sites. Builds a per-site command with that site's data connections.
    /// Returns per-site result matrix.
    /// </summary>
    public async Task<Result<ArtifactDeploymentSummary>> DeployToAllSitesAsync(
        string user,
        CancellationToken cancellationToken = default)
    {
        var sites = await _siteRepo.GetAllSitesAsync(cancellationToken);
        if (sites.Count == 0)
            return Result<ArtifactDeploymentSummary>.Failure("No sites configured.");

        var deploymentId = Guid.NewGuid().ToString("N");
        var perSiteResults = new Dictionary<string, SiteArtifactResult>();

        // Build per-site commands sequentially (DbContext is not thread-safe)
        var siteCommands = new Dictionary<int, DeployArtifactsCommand>();
        foreach (var site in sites)
        {
            siteCommands[site.Id] = await BuildDeployArtifactsCommandAsync(site.Id, cancellationToken);
        }

        // Deploy to each site in parallel with per-site timeout
        var tasks = sites.Select(async site =>
        {
            try
            {
                using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                cts.CancelAfter(_options.ArtifactDeploymentTimeoutPerSite);

                var command = siteCommands[site.Id];

                _logger.LogInformation(
                    "Deploying artifacts to site {SiteId} ({SiteName}), deploymentId={DeploymentId}",
                    site.SiteIdentifier, site.Name, command.DeploymentId);

                var response = await _communicationService.DeployArtifactsAsync(
                    site.SiteIdentifier, command, cts.Token);

                return new SiteArtifactResult(
                    site.SiteIdentifier, site.Name, response.Success, response.ErrorMessage);
            }
            catch (Exception ex) when (ex is TimeoutException or OperationCanceledException or TaskCanceledException)
            {
                _logger.LogWarning(
                    "Artifact deployment to site {SiteId} timed out: {Error}",
                    site.SiteIdentifier, ex.Message);

                return new SiteArtifactResult(
                    site.SiteIdentifier, site.Name, false, $"Timeout: {ex.Message}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex,
                    "Artifact deployment to site {SiteId} failed",
                    site.SiteIdentifier);

                return new SiteArtifactResult(
                    site.SiteIdentifier, site.Name, false, ex.Message);
            }
        }).ToList();

        var results = await Task.WhenAll(tasks);

        foreach (var result in results)
        {
            perSiteResults[result.SiteId] = result;
        }

        // Persist the system artifact deployment record
        var record = new SystemArtifactDeploymentRecord("Artifacts", user)
        {
            DeployedAt = DateTimeOffset.UtcNow,
            PerSiteStatus = JsonSerializer.Serialize(perSiteResults)
        };
        await _deploymentRepo.AddSystemArtifactDeploymentAsync(record, cancellationToken);
        await _deploymentRepo.SaveChangesAsync(cancellationToken);

        var summary = new ArtifactDeploymentSummary(
            deploymentId,
            results.ToList(),
            results.Count(r => r.Success),
            results.Count(r => !r.Success));

        await _auditService.LogAsync(user, "DeployArtifacts", "SystemArtifact",
            deploymentId, "Artifacts",
            new { summary.SuccessCount, summary.FailureCount },
            cancellationToken);

        return Result<ArtifactDeploymentSummary>.Success(summary);
    }

    /// <summary>
    /// WP-7: Retry artifact deployment to a specific site that previously failed.
    /// </summary>
    public async Task<Result<SiteArtifactResult>> RetryForSiteAsync(
        int siteDbId,
        string siteIdentifier,
        string user,
        CancellationToken cancellationToken = default)
    {
        try
        {
            using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            cts.CancelAfter(_options.ArtifactDeploymentTimeoutPerSite);

            var command = await BuildDeployArtifactsCommandAsync(siteDbId, cts.Token);
            var response = await _communicationService.DeployArtifactsAsync(siteIdentifier, command, cts.Token);

            var result = new SiteArtifactResult(siteIdentifier, siteIdentifier, response.Success, response.ErrorMessage);

            await _auditService.LogAsync(user, "RetryArtifactDeployment", "SystemArtifact",
                command.DeploymentId, siteIdentifier, new { response.Success }, cancellationToken);

            return response.Success
                ? Result<SiteArtifactResult>.Success(result)
                : Result<SiteArtifactResult>.Failure(response.ErrorMessage ?? "Retry failed.");
        }
        catch (Exception ex)
        {
            return Result<SiteArtifactResult>.Failure($"Retry failed for site {siteIdentifier}: {ex.Message}");
        }
    }
}

/// <summary>
/// WP-7: Per-site result for artifact deployment.
/// </summary>
public record SiteArtifactResult(
    string SiteId,
    string SiteName,
    bool Success,
    string? ErrorMessage);

/// <summary>
/// WP-7: Summary of system-wide artifact deployment with per-site results.
/// </summary>
public record ArtifactDeploymentSummary(
    string DeploymentId,
    IReadOnlyList<SiteArtifactResult> SiteResults,
    int SuccessCount,
    int FailureCount);
