using ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Types;

namespace ZB.MOM.WW.ScadaBridge.TemplateEngine;

/// <summary>
/// Shared Script CRUD.
/// System-wide scripts not associated with templates.
/// Same parameter/return definition structure as template scripts.
/// Includes syntax/structural validation (basic C# compilation check).
/// </summary>
public class SharedScriptService
{
    private readonly ITemplateEngineRepository _repository;
    private readonly IAuditService _auditService;

    /// <summary>
    /// Initializes a new instance of the <see cref="SharedScriptService"/> class.
    /// </summary>
    /// <param name="repository">The template engine repository for data access.</param>
    /// <param name="auditService">The audit service for logging operations.</param>
    public SharedScriptService(ITemplateEngineRepository repository, IAuditService auditService)
    {
        _repository = repository ?? throw new ArgumentNullException(nameof(repository));
        _auditService = auditService ?? throw new ArgumentNullException(nameof(auditService));
    }

    /// <summary>
    /// Creates a new shared script.
    /// </summary>
    /// <param name="name">The shared script name.</param>
    /// <param name="code">The shared script code.</param>
    /// <param name="parameterDefinitions">Optional parameter definitions JSON.</param>
    /// <param name="returnDefinition">Optional return definition JSON.</param>
    /// <param name="user">The user creating the script.</param>
    /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
    /// <returns>A result containing the created script or an error message.</returns>
    public async Task<Result<SharedScript>> CreateSharedScriptAsync(
        string name,
        string code,
        string? parameterDefinitions,
        string? returnDefinition,
        string user,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(name))
            return Result<SharedScript>.Failure("Shared script name is required.");

        if (string.IsNullOrWhiteSpace(code))
            return Result<SharedScript>.Failure("Shared script code is required.");

        // Check unique name
        var existing = await _repository.GetSharedScriptByNameAsync(name, cancellationToken);
        if (existing != null)
            return Result<SharedScript>.Failure($"A shared script named '{name}' already exists.");

        // Syntax/structural validation
        var syntaxError = ValidateSyntax(code);
        if (syntaxError != null)
            return Result<SharedScript>.Failure(syntaxError);

        var script = new SharedScript(name, code)
        {
            ParameterDefinitions = parameterDefinitions,
            ReturnDefinition = returnDefinition
        };

        // Save the entity first so EF Core populates the
        // auto-generated key, then write the audit row with the real
        // script.Id, then save the audit row. The pre-fix order logged
        // EntityId = "0" because the audit row was queued before
        // SaveChangesAsync ran.
        await _repository.AddSharedScriptAsync(script, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);
        await _auditService.LogAsync(user, "Create", "SharedScript", script.Id.ToString(), name, script, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<SharedScript>.Success(script);
    }

    /// <summary>
    /// Updates an existing shared script.
    /// </summary>
    /// <param name="scriptId">The shared script ID to update.</param>
    /// <param name="code">The updated shared script code.</param>
    /// <param name="parameterDefinitions">Optional updated parameter definitions JSON.</param>
    /// <param name="returnDefinition">Optional updated return definition JSON.</param>
    /// <param name="user">The user updating the script.</param>
    /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
    /// <returns>A result containing the updated script or an error message.</returns>
    public async Task<Result<SharedScript>> UpdateSharedScriptAsync(
        int scriptId,
        string code,
        string? parameterDefinitions,
        string? returnDefinition,
        string user,
        CancellationToken cancellationToken = default)
    {
        var script = await _repository.GetSharedScriptByIdAsync(scriptId, cancellationToken);
        if (script == null)
            return Result<SharedScript>.Failure($"Shared script with ID {scriptId} not found.");

        if (string.IsNullOrWhiteSpace(code))
            return Result<SharedScript>.Failure("Shared script code is required.");

        // Syntax/structural validation
        var syntaxError = ValidateSyntax(code);
        if (syntaxError != null)
            return Result<SharedScript>.Failure(syntaxError);

        script.Code = code;
        script.ParameterDefinitions = parameterDefinitions;
        script.ReturnDefinition = returnDefinition;

        await _repository.UpdateSharedScriptAsync(script, cancellationToken);
        await _auditService.LogAsync(user, "Update", "SharedScript", scriptId.ToString(), script.Name, script, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<SharedScript>.Success(script);
    }

    /// <summary>
    /// Deletes a shared script.
    /// </summary>
    /// <param name="scriptId">The shared script ID to delete.</param>
    /// <param name="user">The user deleting the script.</param>
    /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
    /// <returns>A result containing true if successful or an error message.</returns>
    public async Task<Result<bool>> DeleteSharedScriptAsync(
        int scriptId,
        string user,
        CancellationToken cancellationToken = default)
    {
        var script = await _repository.GetSharedScriptByIdAsync(scriptId, cancellationToken);
        if (script == null)
            return Result<bool>.Failure($"Shared script with ID {scriptId} not found.");

        await _repository.DeleteSharedScriptAsync(scriptId, cancellationToken);
        await _auditService.LogAsync(user, "Delete", "SharedScript", scriptId.ToString(), script.Name, null, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<bool>.Success(true);
    }

    /// <summary>
    /// Gets a shared script by ID.
    /// </summary>
    /// <param name="scriptId">The shared script ID.</param>
    /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
    /// <returns>The shared script if found; null otherwise.</returns>
    public async Task<SharedScript?> GetSharedScriptByIdAsync(int scriptId, CancellationToken cancellationToken = default)
    {
        return await _repository.GetSharedScriptByIdAsync(scriptId, cancellationToken);
    }

    /// <summary>
    /// Gets all shared scripts.
    /// </summary>
    /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
    /// <returns>A read-only list of all shared scripts.</returns>
    public async Task<IReadOnlyList<SharedScript>> GetAllSharedScriptsAsync(CancellationToken cancellationToken = default)
    {
        return await _repository.GetAllSharedScriptsAsync(cancellationToken);
    }

    /// <summary>
    /// Basic structural validation of C# script code.
    /// Checks for balanced braces/brackets/parentheses. The scan is string- and
    /// comment-aware (see <see cref="Validation.CSharpDelimiterScanner"/>) so a
    /// delimiter inside a regular/verbatim/interpolated/raw string literal, a
    /// char literal, or a comment does not produce a false syntax error.
    /// Full Roslyn compilation would be added in a later phase when the scripting sandbox is available.
    /// </summary>
    /// <param name="code">The C# code to validate.</param>
    /// <returns>An error message if validation fails; null if valid.</returns>
    internal static string? ValidateSyntax(string code)
    {
        if (string.IsNullOrWhiteSpace(code))
            return "Script code cannot be empty.";

        return Validation.CSharpDelimiterScanner.Scan(code) switch
        {
            Validation.CSharpDelimiterScanner.Mismatch.None => null,
            Validation.CSharpDelimiterScanner.Mismatch.UnexpectedCloseBrace =>
                "Syntax error: unmatched closing brace '}'.",
            Validation.CSharpDelimiterScanner.Mismatch.UnclosedBrace =>
                "Syntax error: unmatched opening brace '{'.",
            Validation.CSharpDelimiterScanner.Mismatch.UnexpectedCloseBracket =>
                "Syntax error: unmatched closing bracket ']'.",
            Validation.CSharpDelimiterScanner.Mismatch.UnclosedBracket =>
                "Syntax error: unmatched opening bracket '['.",
            Validation.CSharpDelimiterScanner.Mismatch.UnexpectedCloseParen =>
                "Syntax error: unmatched closing parenthesis ')'.",
            Validation.CSharpDelimiterScanner.Mismatch.UnclosedParen =>
                "Syntax error: unmatched opening parenthesis '('.",
            Validation.CSharpDelimiterScanner.Mismatch.UnclosedBlockComment =>
                "Syntax error: unclosed block comment.",
            Validation.CSharpDelimiterScanner.Mismatch.UnterminatedString =>
                "Syntax error: unterminated string literal.",
            Validation.CSharpDelimiterScanner.Mismatch.UnterminatedChar =>
                "Syntax error: unterminated character literal.",
            _ => null,
        };
    }
}
