# frozen_string_literal: true

# Copyright (c) [2025] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require_relative "../agama/storage/storage_helpers"
require "agama/config"
require "agama/storage/config"
require "agama/storage/config_conversions/from_json"
require "agama/storage/system"
require "y2storage"
require "y2storage/agama_proposal"

describe Y2Storage::AgamaProposal do
  using Y2Storage::Refinements::SizeCasts
  include Agama::RSpec::StorageHelpers

  subject(:proposal) do
    described_class.new(config, storage_system, issues_list: issues_list)
  end

  let(:storage_system) { Agama::Storage::System.new }

  let(:config) { config_from_json }

  let(:config_from_json) do
    Agama::Storage::ConfigConversions::FromJSON
      .new(config_json)
      .convert
  end

  let(:issues_list) { [] }

  before do
    mock_storage(devicegraph: scenario)
    # To speed-up the tests
    allow(Y2Storage::EncryptionMethod::TPM_FDE).to receive(:possible?).and_return(true)
  end

  let(:scenario) { "empty-hd-50GiB.yaml" }

  describe "#propose" do
    context "when the config defines a new LVM using a new RAID as PV" do
      let(:scenario) { "disks.yaml" }

      let(:config_json) do
        {
          drives:       [
            {
              partitions: [
                { search: "*", delete: true },
                { alias: "sda1", size: { min: "1 MiB" } }
              ]
            },
            {
              partitions: [
                { search: "*", delete: true },
                { alias: "sdb1", size: { min: "1 MiB" } }
              ]
            }
          ],
          mdRaids:      [
            {
              level:   "raid1",
              alias:   "md",
              devices: ["sda1", "sdb1"]
            }
          ],
          volumeGroups: [
            {
              name:            "system",
              physicalVolumes: ["md"],
              logicalVolumes:  [
                { name: "root", filesystem: { path: "/" } },
                { name: "swap", filesystem: { path: "swap" } }
              ]
            }
          ],
          boot:         { configure: false }
        }
      end

      it "proposes the expected devices" do
        devicegraph = proposal.propose

        expect(devicegraph.md_raids).to contain_exactly(
          an_object_having_attributes(name: "/dev/md0", md_level: Y2Storage::MdLevel::RAID1)
        )
        expect(devicegraph.lvm_vgs).to contain_exactly(
          an_object_having_attributes(vg_name: "system")
        )
        vg = devicegraph.lvm_vgs.first
        expect(vg.lvm_lvs.map(&:name)).to contain_exactly(
          "/dev/system/root", "/dev/system/swap"
        )
        expect(vg.lvm_pvs.size).to eq 1
        expect(vg.lvm_pvs.first.blk_device.name).to eq "/dev/md0"
      end
    end

    context "when the config defines a new LVM generating PVs on a new RAID" do
      let(:scenario) { "disks.yaml" }

      let(:config_json) do
        {
          drives:       [
            {
              partitions: [
                { search: "*", delete: true },
                { alias: "sda1", size: { min: "1 MiB" } }
              ]
            },
            {
              partitions: [
                { search: "*", delete: true },
                { alias: "sdb1", size: { min: "1 MiB" } }
              ]
            }
          ],
          mdRaids:      [
            {
              level:   "raid1",
              alias:   "md",
              devices: ["sda1", "sdb1"]
            }
          ],
          volumeGroups: [
            {
              name:            "system",
              physicalVolumes: [
                { generate: { targetDevices: ["md"] } }
              ],
              logicalVolumes:  [
                { name: "root", size: { min: "10 GiB" }, filesystem: { path: "/" } },
                { name: "swap", size: "2 GiB", filesystem: { path: "swap" } }
              ]
            }
          ],
          boot:         { configure: false }
        }
      end

      it "proposes the expected devices" do
        devicegraph = proposal.propose

        expect(devicegraph.md_raids).to contain_exactly(
          an_object_having_attributes(name: "/dev/md0", md_level: Y2Storage::MdLevel::RAID1)
        )
        expect(devicegraph.lvm_vgs).to contain_exactly(
          an_object_having_attributes(vg_name: "system")
        )
        vg = devicegraph.lvm_vgs.first
        expect(vg.lvm_lvs.map(&:name)).to contain_exactly(
          "/dev/system/root", "/dev/system/swap"
        )
        expect(vg.lvm_pvs.size).to eq 1
        expect(vg.lvm_pvs.first.blk_device.name).to eq "/dev/md0p1"
      end
    end

    context "when the config defines several LVM generating PVs on several new RAIDs" do
      let(:scenario) { "disks.yaml" }

      let(:config_json) do
        {
          drives:       [
            {
              partitions: [
                { search: "*", delete: true },
                { alias: "sda1", size: { min: "1 MiB" } },
                { alias: "sda2", size: "10 GiB" }
              ]
            },
            {
              partitions: [
                { search: "*", delete: true },
                { alias: "sdb1", size: { min: "1 MiB" } },
                { alias: "sdb2", size: "10 GiB" }
              ]
            }
          ],
          mdRaids:      [
            {
              level:   "raid1",
              alias:   "md0",
              devices: ["sda1", "sdb1"]
            },
            {
              level:      "raid0",
              alias:      "md1",
              devices:    ["sdb2", "sda2"],
              partitions: [
                { filesystem: { path: "/extra" }, size: "1GiB" }
              ]
            }
          ],
          volumeGroups: [
            {
              name:            "system",
              physicalVolumes: [
                { generate: { targetDevices: ["md0", "md1"] } }
              ],
              logicalVolumes:  [
                { name: "root", size: { min: "20 GiB" }, filesystem: { path: "/" } },
                { name: "swap", size: "1 GiB", filesystem: { path: "swap" } }
              ]
            },
            {
              name:            "vg1",
              physicalVolumes: [
                {
                  generate: {
                    targetDevices: ["md1"],
                    encryption:    { luks2: { password: "s3cr3t" } }
                  }
                }
              ],
              logicalVolumes:  [
                {
                  name:       "home",
                  filesystem: {
                    path: "/home",
                    type: "xfs"
                  },
                  size:       "3 GiB"
                }
              ]
            }
          ],
          boot:         { configure: false }
        }
      end

      it "proposes the expected devices" do
        devicegraph = proposal.propose

        expect(devicegraph.md_raids).to contain_exactly(
          an_object_having_attributes(name: "/dev/md0", md_level: Y2Storage::MdLevel::RAID1),
          an_object_having_attributes(name: "/dev/md1", md_level: Y2Storage::MdLevel::RAID0)
        )
        md0 = devicegraph.find_by_any_name("/dev/md0")
        md0_formatted = md0.partitions.select(&:formatted?)
        expect(md0_formatted.map(&:filesystem).map(&:mount_path)).to include "/extra"

        expect(devicegraph.lvm_vgs).to contain_exactly(
          an_object_having_attributes(vg_name: "system"),
          an_object_having_attributes(vg_name: "vg1")
        )

        vg_system = devicegraph.find_by_any_name("/dev/system")
        expect(vg_system.lvm_lvs.map(&:name)).to contain_exactly(
          "/dev/system/root", "/dev/system/swap"
        )
        pvs_system = vg_system.lvm_pvs.map(&:blk_device)
        expect(pvs_system.map(&:name)).to contain_exactly("/dev/md0p2", "/dev/md1p2")

        vg1 = devicegraph.find_by_any_name("/dev/vg1")
        expect(vg1.lvm_lvs.map(&:name)).to contain_exactly("/dev/vg1/home")
        pvs_vg1 = vg1.lvm_pvs.map(&:blk_device)
        expect(pvs_vg1.map(&:name)).to contain_exactly("/dev/mapper/cr_md1p1")
      end
    end
  end
end
