require "ipaddr"

RSpec.describe "generic manifest validations" do
  %w[prod prod-lon stg-lon].each do |env|
    context "for the #{env} environment" do
      let(:manifest) { manifest_for_env(env) }
      let(:cloud_config) { cloud_config_with_defaults }

      specify "it must have a name" do
        expect(manifest["name"]).to match(/\S+/)
      end

      it "sets stemcell versions as strings" do
        manifest.fetch("stemcells").each do |stemcell|
          expect(stemcell.fetch("version")).to be_a(String)
        end
      end

      describe "name uniqueness" do
        %w[
          instance_groups
          releases
        ].each do |resource_type|
          specify "all #{resource_type} have a unique name" do
            all_resource_names = manifest.fetch(resource_type).map { |r| r["name"] }

            duplicated_names = all_resource_names.select { |n| all_resource_names.count(n) > 1 }.uniq
            expect(duplicated_names).to be_empty,
              "found duplicate names (#{duplicated_names.join(',')}) for #{resource_type}"
          end
        end
      end

      specify "all instance_groups have a bosh password set" do
        missing = []
        manifest.fetch("instance_groups").each do |ig|
          pw = ig.dig("env", "bosh", "password")
          missing << ig["name"] if pw.nil? || pw.empty?
        end
        expect(missing).to be_empty,
          "Expected instance_groups #{missing.inspect} to have env.bosh.password set"
      end

      describe "jobs cross-references" do
        specify "all jobs reference vm_types that exist" do
          vm_type_names = cloud_config["vm_types"].map { |r| r["name"] }
          manifest["instance_groups"].each do |job|
            expect(vm_type_names).to include(job["vm_type"]),
              "vm_type #{job['vm_type']} not found for job #{job['name']}"
          end
        end

        specify "all jobs reference vm_extensions that exist" do
          vm_extension_names = cloud_config.fetch("vm_extensions", []).map { |r| r["name"] }
          manifest["instance_groups"].each do |job|
            job.fetch("vm_extensions", []).each do |extension|
              expect(vm_extension_names).to include(extension),
                "vm_extension '#{extension}' not found for job #{job['name']}"
            end
          end
        end

        specify "all jobs reference stemcells that exist" do
          stemcell_names = manifest["stemcells"].map { |r| r["alias"] }
          manifest["instance_groups"].each do |job|
            expect(job.key?("stemcell")).to be(true),
              "No stemcell defined for job #{job['name']}. You must add a stemcell to this job."
            expect(stemcell_names).to include(job["stemcell"]),
              "stemcell #{job['stemcell']} not found for job #{job['name']}. This value should correspond to `stemcells.*.alias`."
          end
        end

        specify "all jobs reference availability zones that exist" do
          azs_names = cloud_config["azs"].map { |r| r["name"] }
          manifest["instance_groups"].each do |job|
            expect(job.key?("azs")).to be(true),
              "No azs key defined for job #{job['name']}. You must add some availability zones."
            job["azs"].each do |az|
              expect(azs_names).to include(az),
                "AZ #{az} not found for job #{job['name']}. Check this az exists in the Cloud Config."
            end
          end
        end

        specify "all vm jobs reference releases that exist" do
          release_names = manifest["releases"].map { |r| r["name"] }

          manifest["instance_groups"].each do |instance_group|
            instance_group["jobs"].each do |job|
              expect(release_names).to include(job["release"]),
                "release #{job['release']} not found for job #{job['name']} in instance_group #{instance_group['name']}"
            end
          end
        end

        describe "networks" do
          let(:networks_by_name) do
            cloud_config["networks"].each_with_object({}) { |net, result| result[net["name"]] = net }
          end
          let(:network_names) { networks_by_name.keys }

          specify "all jobs reference networks that exist" do
            manifest["instance_groups"].each do |job|
              job["networks"].each do |network|
                expect(network_names).to include(network["name"]),
                  "network #{network['name']} not found for job #{job['name']}"
              end
            end
          end
        end

        specify "all jobs reference disk_types that exist" do
          disk_type_names = cloud_config.fetch("disk_types", {}).map { |p| p["name"] }

          manifest["instance_groups"].each do |job|
            next unless job["persistent_disk_type"]

            expect(disk_type_names).to include(job["persistent_disk_type"]),
              "disk_type #{job['persistent_disk_type']} not found for job #{job['name']}"
          end
        end
      end
    end
  end
end
