// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using AWS.Deploy.Common.Data;
using AWS.Deploy.Common.Extensions;

namespace AWS.Deploy.Common.Recipes.Validation
{
    /// <summary>
    /// Validates that the selected security groups are part of the selected VPC
    /// </summary>
    public class SecurityGroupsInVpcValidator : IOptionSettingItemValidator
    {
        private static readonly string defaultValidationFailedMessage = "The selected security groups are not part of the selected VPC.";

        /// <summary>
        /// Path to the OptionSetting that stores a selected Vpc Id
        /// </summary>
        public string VpcId { get; set; } = "";

        /// <summary>
        /// Path to the OptionSetting that determines if the default VPC should be used
        /// </summary>
        public string IsDefaultVpcOptionSettingId { get; set; } = "";

        public string ValidationFailedMessage { get; set; } = defaultValidationFailedMessage;

        private readonly IAWSResourceQueryer _awsResourceQueryer;
        private readonly IOptionSettingHandler _optionSettingHandler;

        public SecurityGroupsInVpcValidator(IAWSResourceQueryer awsResourceQueryer, IOptionSettingHandler optionSettingHandler)
        {
            _awsResourceQueryer = awsResourceQueryer;
            _optionSettingHandler = optionSettingHandler;
        }

        public async Task<ValidationResult> Validate(object input, Recommendation recommendation, OptionSettingItem optionSettingItem)
        {
            if (string.IsNullOrEmpty(VpcId))
                return ValidationResult.Failed($"The '{nameof(SecurityGroupsInVpcValidator)}' validator is missing the '{nameof(VpcId)}' configuration.");

            var vpcId = "";

            // The ECS Fargate recipes expose a separate radio button to select the default VPC which is mutually exclusive
            // with specifying an explicit VPC Id. Because we give preference to "UseDefault" in the CDK project,
            // we should do so here as well and validate the security groups against the default VPC if it's selected.
            if (!string.IsNullOrEmpty(IsDefaultVpcOptionSettingId))
            {
                var isDefaultVpcOptionSetting = _optionSettingHandler.GetOptionSetting(recommendation, IsDefaultVpcOptionSettingId);
                var shouldUseDefaultVpc = _optionSettingHandler.GetOptionSettingValue<bool>(recommendation, isDefaultVpcOptionSetting);

                if (shouldUseDefaultVpc)
                {
                    vpcId = (await _awsResourceQueryer.GetDefaultVpc()).VpcId;
                }
            }

            // If the "Use default?" option doesn't exist in the recipe, or it does and was false, or
            // we failed to look up the default VPC, then use the explicity VPC Id
            if (string.IsNullOrEmpty(vpcId))
            {
                var vpcIdSetting = _optionSettingHandler.GetOptionSetting(recommendation, VpcId);
                vpcId = _optionSettingHandler.GetOptionSettingValue<string>(recommendation, vpcIdSetting);
            }

            if (string.IsNullOrEmpty(vpcId))
                return ValidationResult.Failed("The VpcId setting is not set or is empty. Make sure to set the VPC Id first.");

            var securityGroupIds = (await _awsResourceQueryer.DescribeSecurityGroups(vpcId)).Select(x => x.GroupId);

            // The ASP.NET Fargate recipe uses a list of security groups
            if (input?.TryDeserialize<SortedSet<string>>(out var inputList) ?? false)
            {
                var invalidSecurityGroups = new List<string>();
                foreach (var securityGroup in inputList!)
                {
                    if (!securityGroupIds.Contains(securityGroup))
                        invalidSecurityGroups.Add(securityGroup);
                }

                if (invalidSecurityGroups.Any())
                {
                    return ValidationResult.Failed($"The selected security group(s) ({string.Join(", ", invalidSecurityGroups)}) " +
                        $"are invalid since they do not belong to the currently selected VPC {vpcId}.");
                }

                return ValidationResult.Valid();
            }

            // The Console ECS Fargate Service recipe uses a comma-separated string, which will fall through the TryDeserialize above
            if (input is string)
            {
                // Security groups aren't required
                if (string.IsNullOrEmpty(input.ToString()))
                {
                    return ValidationResult.Valid();
                }

                var securityGroupList = input.ToString()?.Split(',') ?? new string[0];
                var invalidSecurityGroups = new List<string>();

                foreach (var securityGroup in securityGroupList)
                {
                    if (!securityGroupIds.Contains(securityGroup))
                        invalidSecurityGroups.Add(securityGroup);
                }

                if (invalidSecurityGroups.Any())
                {
                    return ValidationResult.Failed($"The selected security group(s) ({string.Join(", ", invalidSecurityGroups)}) " +
                        $"are invalid since they do not belong to the currently selected VPC {vpcId}.");
                }

                return ValidationResult.Valid();
            }

            return new ValidationResult
            {
                IsValid = securityGroupIds.Contains(input?.ToString()),
                ValidationFailedMessage = ValidationFailedMessage
            };
        }
    }
}