using ScadaLink.Commons.Entities.Templates;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.Commons.Types;

namespace ScadaLink.TemplateEngine;

/// <summary>
/// Core service for Template Engine operations.
/// Covers CRUD for templates and their members (attributes, alarms, scripts, compositions),
/// inheritance and composition rules, override/locking validation, collision detection, and acyclicity enforcement.
/// </summary>
public class TemplateService
{
    private readonly ITemplateEngineRepository _repository;
    private readonly IAuditService _auditService;

    public TemplateService(ITemplateEngineRepository repository, IAuditService auditService)
    {
        _repository = repository ?? throw new ArgumentNullException(nameof(repository));
        _auditService = auditService ?? throw new ArgumentNullException(nameof(auditService));
    }

    // ========================================================================
    // WP-1: Template CRUD with Inheritance
    // ========================================================================

    public async Task<Result<Template>> CreateTemplateAsync(
        string name,
        string? description,
        int? parentTemplateId,
        string user,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(name))
            return Result<Template>.Failure("Template name is required.");

        // Validate parent exists if specified
        if (parentTemplateId.HasValue)
        {
            var parent = await _repository.GetTemplateByIdAsync(parentTemplateId.Value, cancellationToken);
            if (parent == null)
                return Result<Template>.Failure($"Parent template with ID {parentTemplateId.Value} not found.");
        }

        var template = new Template(name)
        {
            Description = description,
            ParentTemplateId = parentTemplateId
        };

        // Check acyclicity (inheritance) — for new templates this is mostly a parent-exists check,
        // but we validate anyway for consistency
        if (parentTemplateId.HasValue)
        {
            var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
            // The new template doesn't exist yet, so we simulate by adding it to the list
            // with a temporary ID. Since it has no children yet, the only cycle would be
            // if parentTemplateId somehow pointed at itself (already handled above).
        }

        await _repository.AddTemplateAsync(template, cancellationToken);
        await _auditService.LogAsync(user, "Create", "Template", "0", name, template, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<Template>.Success(template);
    }

    public async Task<Result<Template>> UpdateTemplateAsync(
        int templateId,
        string name,
        string? description,
        int? parentTemplateId,
        string user,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(name))
            return Result<Template>.Failure("Template name is required.");

        var template = await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
        if (template == null)
            return Result<Template>.Failure($"Template with ID {templateId} not found.");

        // Validate parent change
        if (parentTemplateId.HasValue && parentTemplateId.Value != (template.ParentTemplateId ?? 0))
        {
            var parent = await _repository.GetTemplateByIdAsync(parentTemplateId.Value, cancellationToken);
            if (parent == null)
                return Result<Template>.Failure($"Parent template with ID {parentTemplateId.Value} not found.");

            // Check inheritance acyclicity
            var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
            var cycleError = CycleDetector.DetectInheritanceCycle(templateId, parentTemplateId.Value, allTemplates);
            if (cycleError != null)
                return Result<Template>.Failure(cycleError);

            // Check cross-graph cycle
            var crossCycleError = CycleDetector.DetectCrossGraphCycle(templateId, parentTemplateId, null, allTemplates);
            if (crossCycleError != null)
                return Result<Template>.Failure(crossCycleError);
        }

        template.Name = name;
        template.Description = description;
        template.ParentTemplateId = parentTemplateId;

        // Check for naming collisions after the change
        var collisionResult = await ValidateCollisionsAsync(template, cancellationToken);
        if (collisionResult != null)
            return Result<Template>.Failure(collisionResult);

        await _repository.UpdateTemplateAsync(template, cancellationToken);
        await _auditService.LogAsync(user, "Update", "Template", templateId.ToString(), name, template, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<Template>.Success(template);
    }

    public async Task<Result<bool>> DeleteTemplateAsync(
        int templateId,
        string user,
        CancellationToken cancellationToken = default)
    {
        var template = await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
        if (template == null)
            return Result<bool>.Failure($"Template with ID {templateId} not found.");

        // Check for instances referencing this template
        var instances = await _repository.GetInstancesByTemplateIdAsync(templateId, cancellationToken);
        if (instances.Count > 0)
            return Result<bool>.Failure(
                $"Cannot delete template '{template.Name}': it is referenced by {instances.Count} instance(s).");

        // Check for child templates inheriting from this template
        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
        var children = allTemplates.Where(t => t.ParentTemplateId == templateId).ToList();
        if (children.Count > 0)
            return Result<bool>.Failure(
                $"Cannot delete template '{template.Name}': it is inherited by {children.Count} child template(s): " +
                string.Join(", ", children.Select(c => $"'{c.Name}'")));

        // Check for templates composing this template
        var composedBy = allTemplates
            .Where(t => t.Compositions.Any(c => c.ComposedTemplateId == templateId))
            .ToList();
        if (composedBy.Count > 0)
            return Result<bool>.Failure(
                $"Cannot delete template '{template.Name}': it is composed by {composedBy.Count} template(s): " +
                string.Join(", ", composedBy.Select(c => $"'{c.Name}'")));

        await _repository.DeleteTemplateAsync(templateId, cancellationToken);
        await _auditService.LogAsync(user, "Delete", "Template", templateId.ToString(), template.Name, null, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

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

    public async Task<Template?> GetTemplateByIdAsync(int templateId, CancellationToken cancellationToken = default)
    {
        return await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
    }

    public async Task<IReadOnlyList<Template>> GetAllTemplatesAsync(CancellationToken cancellationToken = default)
    {
        return await _repository.GetAllTemplatesAsync(cancellationToken);
    }

    // ========================================================================
    // WP-2: Attribute Definitions with Lock Flags
    // ========================================================================

    public async Task<Result<TemplateAttribute>> AddAttributeAsync(
        int templateId,
        TemplateAttribute attribute,
        string user,
        CancellationToken cancellationToken = default)
    {
        var template = await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
        if (template == null)
            return Result<TemplateAttribute>.Failure($"Template with ID {templateId} not found.");

        // Check for duplicate name at this template level
        if (template.Attributes.Any(a => a.Name == attribute.Name))
            return Result<TemplateAttribute>.Failure(
                $"Attribute '{attribute.Name}' already exists on template '{template.Name}'.");

        attribute.TemplateId = templateId;

        // If inheriting, validate not trying to add a member that would collide
        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
        var testTemplate = CloneTemplateWithNewAttribute(template, attribute);
        var collisions = CollisionDetector.DetectCollisions(testTemplate, allTemplates);
        if (collisions.Count > 0)
            return Result<TemplateAttribute>.Failure(string.Join(" ", collisions));

        await _repository.AddTemplateAttributeAsync(attribute, cancellationToken);
        await _auditService.LogAsync(user, "Create", "TemplateAttribute", "0", attribute.Name, attribute, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<TemplateAttribute>.Success(attribute);
    }

    public async Task<Result<TemplateAttribute>> UpdateAttributeAsync(
        int attributeId,
        TemplateAttribute proposed,
        string user,
        CancellationToken cancellationToken = default)
    {
        var existing = await _repository.GetTemplateAttributeByIdAsync(attributeId, cancellationToken);
        if (existing == null)
            return Result<TemplateAttribute>.Failure($"Attribute with ID {attributeId} not found.");

        // Validate override rules if this is an override (parent has same-named attribute)
        var template = await _repository.GetTemplateByIdAsync(existing.TemplateId, cancellationToken);
        if (template?.ParentTemplateId != null)
        {
            var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
            var parentMember = TemplateResolver.FindMemberByCanonicalName(
                existing.Name, template.ParentTemplateId.Value, allTemplates);
            if (parentMember != null && parentMember.IsLocked)
                return Result<TemplateAttribute>.Failure(
                    $"Attribute '{existing.Name}' is locked in parent and cannot be overridden.");
        }

        // Validate lock change rules
        var lockError = LockEnforcer.ValidateLockChange(existing.IsLocked, proposed.IsLocked, existing.Name);
        if (lockError != null)
            return Result<TemplateAttribute>.Failure(lockError);

        // Validate fixed-field granularity
        var granularityError = LockEnforcer.ValidateAttributeOverride(existing, proposed);
        if (granularityError != null && existing.IsLocked)
            return Result<TemplateAttribute>.Failure(granularityError);

        // Apply overridable fields
        existing.Value = proposed.Value;
        existing.Description = proposed.Description;
        existing.IsLocked = proposed.IsLocked;
        existing.DataType = proposed.DataType;
        existing.DataSourceReference = proposed.DataSourceReference;

        await _repository.UpdateTemplateAttributeAsync(existing, cancellationToken);
        await _auditService.LogAsync(user, "Update", "TemplateAttribute", attributeId.ToString(), existing.Name, existing, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<TemplateAttribute>.Success(existing);
    }

    public async Task<Result<bool>> DeleteAttributeAsync(
        int attributeId,
        string user,
        CancellationToken cancellationToken = default)
    {
        var attribute = await _repository.GetTemplateAttributeByIdAsync(attributeId, cancellationToken);
        if (attribute == null)
            return Result<bool>.Failure($"Attribute with ID {attributeId} not found.");

        // Cannot remove inherited parent members — only direct members
        var template = await _repository.GetTemplateByIdAsync(attribute.TemplateId, cancellationToken);
        if (template?.ParentTemplateId != null)
        {
            var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
            var parentMember = TemplateResolver.FindMemberByCanonicalName(
                attribute.Name, template.ParentTemplateId.Value, allTemplates);
            if (parentMember != null)
                return Result<bool>.Failure(
                    $"Cannot remove attribute '{attribute.Name}': it is inherited from parent template.");
        }

        await _repository.DeleteTemplateAttributeAsync(attributeId, cancellationToken);
        await _auditService.LogAsync(user, "Delete", "TemplateAttribute", attributeId.ToString(), attribute.Name, null, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

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

    // ========================================================================
    // WP-3: Alarm Definitions
    // ========================================================================

    public async Task<Result<TemplateAlarm>> AddAlarmAsync(
        int templateId,
        TemplateAlarm alarm,
        string user,
        CancellationToken cancellationToken = default)
    {
        var template = await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
        if (template == null)
            return Result<TemplateAlarm>.Failure($"Template with ID {templateId} not found.");

        if (template.Alarms.Any(a => a.Name == alarm.Name))
            return Result<TemplateAlarm>.Failure(
                $"Alarm '{alarm.Name}' already exists on template '{template.Name}'.");

        // Validate priority range
        if (alarm.PriorityLevel < 0 || alarm.PriorityLevel > 1000)
            return Result<TemplateAlarm>.Failure("Alarm priority must be between 0 and 1000.");

        alarm.TemplateId = templateId;

        // Check collisions
        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
        var testTemplate = CloneTemplateWithNewAlarm(template, alarm);
        var collisions = CollisionDetector.DetectCollisions(testTemplate, allTemplates);
        if (collisions.Count > 0)
            return Result<TemplateAlarm>.Failure(string.Join(" ", collisions));

        await _repository.AddTemplateAlarmAsync(alarm, cancellationToken);
        await _auditService.LogAsync(user, "Create", "TemplateAlarm", "0", alarm.Name, alarm, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<TemplateAlarm>.Success(alarm);
    }

    public async Task<Result<TemplateAlarm>> UpdateAlarmAsync(
        int alarmId,
        TemplateAlarm proposed,
        string user,
        CancellationToken cancellationToken = default)
    {
        var existing = await _repository.GetTemplateAlarmByIdAsync(alarmId, cancellationToken);
        if (existing == null)
            return Result<TemplateAlarm>.Failure($"Alarm with ID {alarmId} not found.");

        // Validate priority range
        if (proposed.PriorityLevel < 0 || proposed.PriorityLevel > 1000)
            return Result<TemplateAlarm>.Failure("Alarm priority must be between 0 and 1000.");

        // Validate lock change
        var lockError = LockEnforcer.ValidateLockChange(existing.IsLocked, proposed.IsLocked, existing.Name);
        if (lockError != null)
            return Result<TemplateAlarm>.Failure(lockError);

        // Check parent lock
        var template = await _repository.GetTemplateByIdAsync(existing.TemplateId, cancellationToken);
        if (template?.ParentTemplateId != null)
        {
            var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
            var parentMember = TemplateResolver.FindMemberByCanonicalName(
                existing.Name, template.ParentTemplateId.Value, allTemplates);
            if (parentMember != null && parentMember.IsLocked)
                return Result<TemplateAlarm>.Failure(
                    $"Alarm '{existing.Name}' is locked in parent and cannot be overridden.");
        }

        // Validate fixed fields
        var overrideError = LockEnforcer.ValidateAlarmOverride(existing, proposed);
        if (overrideError != null)
            return Result<TemplateAlarm>.Failure(overrideError);

        // Apply overridable fields
        existing.PriorityLevel = proposed.PriorityLevel;
        existing.TriggerConfiguration = proposed.TriggerConfiguration;
        existing.Description = proposed.Description;
        existing.OnTriggerScriptId = proposed.OnTriggerScriptId;
        existing.IsLocked = proposed.IsLocked;
        // Name and TriggerType are NOT updated (fixed)

        await _repository.UpdateTemplateAlarmAsync(existing, cancellationToken);
        await _auditService.LogAsync(user, "Update", "TemplateAlarm", alarmId.ToString(), existing.Name, existing, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<TemplateAlarm>.Success(existing);
    }

    public async Task<Result<bool>> DeleteAlarmAsync(
        int alarmId,
        string user,
        CancellationToken cancellationToken = default)
    {
        var alarm = await _repository.GetTemplateAlarmByIdAsync(alarmId, cancellationToken);
        if (alarm == null)
            return Result<bool>.Failure($"Alarm with ID {alarmId} not found.");

        var template = await _repository.GetTemplateByIdAsync(alarm.TemplateId, cancellationToken);
        if (template?.ParentTemplateId != null)
        {
            var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
            var parentMember = TemplateResolver.FindMemberByCanonicalName(
                alarm.Name, template.ParentTemplateId.Value, allTemplates);
            if (parentMember != null)
                return Result<bool>.Failure(
                    $"Cannot remove alarm '{alarm.Name}': it is inherited from parent template.");
        }

        await _repository.DeleteTemplateAlarmAsync(alarmId, cancellationToken);
        await _auditService.LogAsync(user, "Delete", "TemplateAlarm", alarmId.ToString(), alarm.Name, null, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

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

    // ========================================================================
    // WP-4: Script Definitions
    // ========================================================================

    public async Task<Result<TemplateScript>> AddScriptAsync(
        int templateId,
        TemplateScript script,
        string user,
        CancellationToken cancellationToken = default)
    {
        var template = await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
        if (template == null)
            return Result<TemplateScript>.Failure($"Template with ID {templateId} not found.");

        if (template.Scripts.Any(s => s.Name == script.Name))
            return Result<TemplateScript>.Failure(
                $"Script '{script.Name}' already exists on template '{template.Name}'.");

        script.TemplateId = templateId;

        // Check collisions
        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
        var testTemplate = CloneTemplateWithNewScript(template, script);
        var collisions = CollisionDetector.DetectCollisions(testTemplate, allTemplates);
        if (collisions.Count > 0)
            return Result<TemplateScript>.Failure(string.Join(" ", collisions));

        await _repository.AddTemplateScriptAsync(script, cancellationToken);
        await _auditService.LogAsync(user, "Create", "TemplateScript", "0", script.Name, script, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

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

    public async Task<Result<TemplateScript>> UpdateScriptAsync(
        int scriptId,
        TemplateScript proposed,
        string user,
        CancellationToken cancellationToken = default)
    {
        var existing = await _repository.GetTemplateScriptByIdAsync(scriptId, cancellationToken);
        if (existing == null)
            return Result<TemplateScript>.Failure($"Script with ID {scriptId} not found.");

        // Validate lock change
        var lockError = LockEnforcer.ValidateLockChange(existing.IsLocked, proposed.IsLocked, existing.Name);
        if (lockError != null)
            return Result<TemplateScript>.Failure(lockError);

        // Check parent lock
        var template = await _repository.GetTemplateByIdAsync(existing.TemplateId, cancellationToken);
        if (template?.ParentTemplateId != null)
        {
            var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
            var parentMember = TemplateResolver.FindMemberByCanonicalName(
                existing.Name, template.ParentTemplateId.Value, allTemplates);
            if (parentMember != null && parentMember.IsLocked)
                return Result<TemplateScript>.Failure(
                    $"Script '{existing.Name}' is locked in parent and cannot be overridden.");
        }

        // Validate fixed fields
        var overrideError = LockEnforcer.ValidateScriptOverride(existing, proposed);
        if (overrideError != null)
            return Result<TemplateScript>.Failure(overrideError);

        // Apply overridable fields
        existing.Code = proposed.Code;
        existing.TriggerType = proposed.TriggerType;
        existing.TriggerConfiguration = proposed.TriggerConfiguration;
        existing.MinTimeBetweenRuns = proposed.MinTimeBetweenRuns;
        existing.ParameterDefinitions = proposed.ParameterDefinitions;
        existing.ReturnDefinition = proposed.ReturnDefinition;
        existing.IsLocked = proposed.IsLocked;
        // Name is NOT updated (fixed)

        await _repository.UpdateTemplateScriptAsync(existing, cancellationToken);
        await _auditService.LogAsync(user, "Update", "TemplateScript", scriptId.ToString(), existing.Name, existing, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<TemplateScript>.Success(existing);
    }

    public async Task<Result<bool>> DeleteScriptAsync(
        int scriptId,
        string user,
        CancellationToken cancellationToken = default)
    {
        var script = await _repository.GetTemplateScriptByIdAsync(scriptId, cancellationToken);
        if (script == null)
            return Result<bool>.Failure($"Script with ID {scriptId} not found.");

        var template = await _repository.GetTemplateByIdAsync(script.TemplateId, cancellationToken);
        if (template?.ParentTemplateId != null)
        {
            var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
            var parentMember = TemplateResolver.FindMemberByCanonicalName(
                script.Name, template.ParentTemplateId.Value, allTemplates);
            if (parentMember != null)
                return Result<bool>.Failure(
                    $"Cannot remove script '{script.Name}': it is inherited from parent template.");
        }

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

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

    // ========================================================================
    // WP-6: Composition with Recursive Nesting
    // ========================================================================

    public async Task<Result<TemplateComposition>> AddCompositionAsync(
        int templateId,
        int composedTemplateId,
        string instanceName,
        string user,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(instanceName))
            return Result<TemplateComposition>.Failure("Instance name is required for composition.");

        var template = await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
        if (template == null)
            return Result<TemplateComposition>.Failure($"Template with ID {templateId} not found.");

        var composedTemplate = await _repository.GetTemplateByIdAsync(composedTemplateId, cancellationToken);
        if (composedTemplate == null)
            return Result<TemplateComposition>.Failure($"Composed template with ID {composedTemplateId} not found.");

        // Check for duplicate instance name
        if (template.Compositions.Any(c => c.InstanceName == instanceName))
            return Result<TemplateComposition>.Failure(
                $"Composition instance name '{instanceName}' already exists on template '{template.Name}'.");

        // Check composition acyclicity
        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
        var cycleError = CycleDetector.DetectCompositionCycle(templateId, composedTemplateId, allTemplates);
        if (cycleError != null)
            return Result<TemplateComposition>.Failure(cycleError);

        // Check cross-graph cycle
        var crossCycleError = CycleDetector.DetectCrossGraphCycle(templateId, null, composedTemplateId, allTemplates);
        if (crossCycleError != null)
            return Result<TemplateComposition>.Failure(crossCycleError);

        var composition = new TemplateComposition(instanceName)
        {
            TemplateId = templateId,
            ComposedTemplateId = composedTemplateId
        };

        // Check for naming collisions with the new composition
        var testTemplate = CloneTemplateWithNewComposition(template, composition);
        var collisions = CollisionDetector.DetectCollisions(testTemplate, allTemplates);
        if (collisions.Count > 0)
            return Result<TemplateComposition>.Failure(string.Join(" ", collisions));

        await _repository.AddTemplateCompositionAsync(composition, cancellationToken);
        await _auditService.LogAsync(user, "Create", "TemplateComposition", "0", instanceName, composition, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<TemplateComposition>.Success(composition);
    }

    public async Task<Result<bool>> DeleteCompositionAsync(
        int compositionId,
        string user,
        CancellationToken cancellationToken = default)
    {
        var composition = await _repository.GetTemplateCompositionByIdAsync(compositionId, cancellationToken);
        if (composition == null)
            return Result<bool>.Failure($"Composition with ID {compositionId} not found.");

        await _repository.DeleteTemplateCompositionAsync(compositionId, cancellationToken);
        await _auditService.LogAsync(user, "Delete", "TemplateComposition", compositionId.ToString(), composition.InstanceName, null, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);

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

    // ========================================================================
    // WP-7: Path-Qualified Canonical Naming (via TemplateResolver)
    // ========================================================================

    /// <summary>
    /// Resolves all effective members for a template using canonical (path-qualified) names.
    /// </summary>
    public async Task<IReadOnlyList<TemplateResolver.ResolvedTemplateMember>> ResolveTemplateMembersAsync(
        int templateId,
        CancellationToken cancellationToken = default)
    {
        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
        return TemplateResolver.ResolveAllMembers(templateId, allTemplates);
    }

    // ========================================================================
    // WP-8/9/10/11: Override validation (integrated into Update* methods above)
    // ========================================================================

    /// <summary>
    /// Validates whether overriding a member by canonical name is allowed.
    /// Used for composition overrides (WP-11) and inheritance overrides (WP-10).
    /// </summary>
    public async Task<Result<bool>> ValidateOverrideAsync(
        int templateId,
        string canonicalName,
        CancellationToken cancellationToken = default)
    {
        var template = await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
        if (template == null)
            return Result<bool>.Failure($"Template with ID {templateId} not found.");

        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
        var members = TemplateResolver.ResolveAllMembers(templateId, allTemplates);
        var member = members.FirstOrDefault(m => m.CanonicalName == canonicalName);

        if (member == null)
            return Result<bool>.Failure($"No member found with canonical name '{canonicalName}'.");

        if (member.IsLocked)
            return Result<bool>.Failure($"Member '{canonicalName}' is locked and cannot be overridden.");

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

    // ========================================================================
    // WP-12: Naming Collision Detection
    // ========================================================================

    /// <summary>
    /// Checks a template for naming collisions across all its members.
    /// </summary>
    public async Task<IReadOnlyList<string>> DetectCollisionsAsync(
        int templateId,
        CancellationToken cancellationToken = default)
    {
        var template = await _repository.GetTemplateByIdAsync(templateId, cancellationToken);
        if (template == null)
            return new[] { $"Template with ID {templateId} not found." };

        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
        return CollisionDetector.DetectCollisions(template, allTemplates);
    }

    // ========================================================================
    // WP-13: Graph Acyclicity
    // ========================================================================

    /// <summary>
    /// Validates that a proposed inheritance or composition change does not create a cycle.
    /// </summary>
    public async Task<Result<bool>> ValidateAcyclicityAsync(
        int templateId,
        int? proposedParentId,
        int? proposedComposedTemplateId,
        CancellationToken cancellationToken = default)
    {
        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);

        if (proposedParentId.HasValue)
        {
            var inheritanceCycle = CycleDetector.DetectInheritanceCycle(templateId, proposedParentId.Value, allTemplates);
            if (inheritanceCycle != null)
                return Result<bool>.Failure(inheritanceCycle);
        }

        if (proposedComposedTemplateId.HasValue)
        {
            var compositionCycle = CycleDetector.DetectCompositionCycle(templateId, proposedComposedTemplateId.Value, allTemplates);
            if (compositionCycle != null)
                return Result<bool>.Failure(compositionCycle);
        }

        var crossCycle = CycleDetector.DetectCrossGraphCycle(templateId, proposedParentId, proposedComposedTemplateId, allTemplates);
        if (crossCycle != null)
            return Result<bool>.Failure(crossCycle);

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

    // ========================================================================
    // Helper methods
    // ========================================================================

    private async Task<string?> ValidateCollisionsAsync(Template template, CancellationToken cancellationToken)
    {
        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
        var collisions = CollisionDetector.DetectCollisions(template, allTemplates);
        if (collisions.Count > 0)
            return string.Join(" ", collisions);
        return null;
    }

    private static Template CloneTemplateWithNewAttribute(Template original, TemplateAttribute newAttr)
    {
        var clone = new Template(original.Name)
        {
            Id = original.Id,
            ParentTemplateId = original.ParentTemplateId,
            Description = original.Description,
        };
        foreach (var a in original.Attributes) clone.Attributes.Add(a);
        clone.Attributes.Add(newAttr);
        foreach (var a in original.Alarms) clone.Alarms.Add(a);
        foreach (var s in original.Scripts) clone.Scripts.Add(s);
        foreach (var c in original.Compositions) clone.Compositions.Add(c);
        return clone;
    }

    private static Template CloneTemplateWithNewAlarm(Template original, TemplateAlarm newAlarm)
    {
        var clone = new Template(original.Name)
        {
            Id = original.Id,
            ParentTemplateId = original.ParentTemplateId,
            Description = original.Description,
        };
        foreach (var a in original.Attributes) clone.Attributes.Add(a);
        foreach (var a in original.Alarms) clone.Alarms.Add(a);
        clone.Alarms.Add(newAlarm);
        foreach (var s in original.Scripts) clone.Scripts.Add(s);
        foreach (var c in original.Compositions) clone.Compositions.Add(c);
        return clone;
    }

    private static Template CloneTemplateWithNewScript(Template original, TemplateScript newScript)
    {
        var clone = new Template(original.Name)
        {
            Id = original.Id,
            ParentTemplateId = original.ParentTemplateId,
            Description = original.Description,
        };
        foreach (var a in original.Attributes) clone.Attributes.Add(a);
        foreach (var a in original.Alarms) clone.Alarms.Add(a);
        foreach (var s in original.Scripts) clone.Scripts.Add(s);
        clone.Scripts.Add(newScript);
        foreach (var c in original.Compositions) clone.Compositions.Add(c);
        return clone;
    }

    private static Template CloneTemplateWithNewComposition(Template original, TemplateComposition newComp)
    {
        var clone = new Template(original.Name)
        {
            Id = original.Id,
            ParentTemplateId = original.ParentTemplateId,
            Description = original.Description,
        };
        foreach (var a in original.Attributes) clone.Attributes.Add(a);
        foreach (var a in original.Alarms) clone.Alarms.Add(a);
        foreach (var s in original.Scripts) clone.Scripts.Add(s);
        foreach (var c in original.Compositions) clone.Compositions.Add(c);
        clone.Compositions.Add(newComp);
        return clone;
    }
}
