using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;

namespace ZB.MOM.WW.OtOpcUa.Admin.Services;

/// <summary>
///     Draft-generation CRUD for <see cref="Script"/> rows — the C# source code referenced
///     by Phase 7 virtual tags and scripted alarms. <see cref="Script.SourceHash"/> is
///     recomputed on every save so Core.Scripting's compile cache sees a fresh key when
///     source changes and reuses the compile when it doesn't.
/// </summary>
public sealed class ScriptService(OtOpcUaConfigDbContext db)
{
    public Task<List<Script>> ListAsync(long generationId, CancellationToken ct) =>
        db.Scripts.AsNoTracking()
            .Where(s => s.GenerationId == generationId)
            .OrderBy(s => s.Name)
            .ToListAsync(ct);

    public Task<Script?> GetAsync(long generationId, string scriptId, CancellationToken ct) =>
        db.Scripts.AsNoTracking()
            .FirstOrDefaultAsync(s => s.GenerationId == generationId && s.ScriptId == scriptId, ct);

    public async Task<Script> AddAsync(long generationId, string name, string sourceCode, CancellationToken ct)
    {
        var s = new Script
        {
            GenerationId = generationId,
            ScriptId = $"scr-{Guid.NewGuid():N}"[..20],
            Name = name,
            SourceCode = sourceCode,
            SourceHash = ComputeHash(sourceCode),
        };
        db.Scripts.Add(s);
        await db.SaveChangesAsync(ct);
        return s;
    }

    public async Task<Script> UpdateAsync(long generationId, string scriptId, string name, string sourceCode, CancellationToken ct)
    {
        var s = await db.Scripts.FirstOrDefaultAsync(x => x.GenerationId == generationId && x.ScriptId == scriptId, ct)
            ?? throw new InvalidOperationException($"Script '{scriptId}' not found in generation {generationId}");
        s.Name = name;
        s.SourceCode = sourceCode;
        s.SourceHash = ComputeHash(sourceCode);
        await db.SaveChangesAsync(ct);
        return s;
    }

    public async Task DeleteAsync(long generationId, string scriptId, CancellationToken ct)
    {
        var s = await db.Scripts.FirstOrDefaultAsync(x => x.GenerationId == generationId && x.ScriptId == scriptId, ct);
        if (s is null) return;
        db.Scripts.Remove(s);
        await db.SaveChangesAsync(ct);
    }

    internal static string ComputeHash(string source)
    {
        var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(source ?? string.Empty));
        return Convert.ToHexString(bytes);
    }
}
