defmodule Mix.Tasks.GenVendorModulesTest do
  use ExUnit.Case, async: false

  import ExUnit.CaptureIO

  alias Mix.Tasks.GenVendorModules

  @moduletag :mix_task

  setup do
    # Save original directory
    original_dir = File.cwd!()

    # Create temporary directories for profiles and vendor modules
    temp_dir = Path.join(System.tmp_dir!(), "gen_vendor_modules_#{System.unique_integer([:positive])}")
    profiles_dir = Path.join([temp_dir, "priv", "profiles", "os_detection"])
    vendors_dir = Path.join([temp_dir, "lib", "towerops", "snmp", "profiles", "vendors"])
    File.mkdir_p!(profiles_dir)
    File.mkdir_p!(vendors_dir)

    on_exit(fn ->
      File.cd!(original_dir)
      File.rm_rf!(temp_dir)
    end)

    {:ok, temp_dir: temp_dir, profiles_dir: profiles_dir, vendors_dir: vendors_dir}
  end

  describe "run/1 with dry-run" do
    test "reports vendors that would be generated in dry-run mode", %{
      temp_dir: temp_dir,
      profiles_dir: profiles_dir,
      vendors_dir: _vendors_dir
    } do
      # Create profile YAML with group
      File.write!(Path.join(profiles_dir, "mikrotik.yaml"), """
      os: mikrotik
      group: mikrotik
      text: MikroTik
      discovery:
        - sysObjectID:
            - .1.3.6.1.4.1.14988.
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run(["--dry-run"])
          end)

        assert output =~ "Found 1 YAML profiles"
        assert output =~ "Grouped into 1 vendors"
        assert output =~ "1 new vendors to generate"
        assert output =~ "mikrotik.ex (Mikrotik)"
        assert output =~ "Profiles: mikrotik"
        assert output =~ "Enterprise OID: 1.3.6.1.4.1.14988"
      end)
    end

    test "reports no work needed when all vendors exist", %{
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir,
      temp_dir: temp_dir
    } do
      # Create profile YAML
      File.write!(Path.join(profiles_dir, "mikrotik.yaml"), """
      os: mikrotik
      group: mikrotik
      text: MikroTik
      """)

      # Create existing vendor module
      File.write!(Path.join(vendors_dir, "mikrotik.ex"), """
      defmodule Towerops.Snmp.Profiles.Vendors.Mikrotik do
        def profile_names, do: ["mikrotik"]
      end
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run(["--dry-run"])
          end)

        assert output =~ "0 new vendors to generate"
        assert output =~ "No new vendors to generate!"
      end)
    end
  end

  describe "run/1 without dry-run" do
    test "generates vendor module from profile with group", %{
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "cisco-ios.yaml"), """
      os: cisco-ios
      group: cisco
      text: Cisco IOS
      discovery:
        - sysObjectID:
            - .1.3.6.1.4.1.9.
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run([])
          end)

        assert output =~ "Generated: lib/towerops/snmp/profiles/vendors/cisco.ex"
        assert output =~ "Generated 1 vendor modules"

        # Verify file was created
        vendor_file = Path.join(vendors_dir, "cisco.ex")
        assert File.exists?(vendor_file)

        # Verify file content
        content = File.read!(vendor_file)
        assert content =~ "defmodule Towerops.Snmp.Profiles.Vendors.Cisco do"
        assert content =~ "@moduledoc"
        assert content =~ "SNMP profile for Cisco devices"
        assert content =~ "@behaviour Towerops.Snmp.Profiles.Vendors.Vendor"
        assert content =~ "def profile_names, do: [\"cisco-ios\"]"
        assert content =~ "# Enterprise OID: 1.3.6.1.4.1.9"
        assert content =~ "def detect_hardware(client_opts)"
        assert content =~ "def wireless_oid_defs"
        assert content =~ "def discover_wireless_sensors(client_opts)"
      end)
    end

    test "generates vendor module from profile without group using filename inference", %{
      temp_dir: temp_dir,
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir
    } do
      File.write!(Path.join(profiles_dir, "cisco_ios.yaml"), """
      os: cisco-ios
      text: Cisco IOS
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run([])
          end)

        # Should infer cisco from filename
        assert output =~ "Generated: lib/towerops/snmp/profiles/vendors/cisco.ex"

        vendor_file = Path.join(vendors_dir, "cisco.ex")
        assert File.exists?(vendor_file)
      end)
    end

    test "groups multiple profiles into single vendor module", %{
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "cisco-ios.yaml"), """
      os: cisco-ios
      group: cisco
      text: Cisco IOS
      """)

      File.write!(Path.join(profiles_dir, "cisco-nxos.yaml"), """
      os: cisco-nxos
      group: cisco
      text: Cisco NX-OS
      """)

      File.write!(Path.join(profiles_dir, "cisco-asa.yaml"), """
      os: cisco-asa
      group: cisco
      text: Cisco ASA
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run([])
          end)

        assert output =~ "Generated 1 vendor modules"

        vendor_file = Path.join(vendors_dir, "cisco.ex")
        content = File.read!(vendor_file)

        # Should include all three profiles
        assert content =~ ~s(["cisco-asa", "cisco-ios", "cisco-nxos"])
      end)
    end

    test "generates modules for multiple vendors", %{
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "mikrotik.yaml"), """
      os: mikrotik
      group: mikrotik
      text: MikroTik
      """)

      File.write!(Path.join(profiles_dir, "ubnt.yaml"), """
      os: ubnt
      group: ubiquiti
      text: Ubiquiti
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run([])
          end)

        assert output =~ "Generated 2 vendor modules"
        assert File.exists?(Path.join(vendors_dir, "mikrotik.ex"))
        assert File.exists?(Path.join(vendors_dir, "ubiquiti.ex"))
      end)
    end

    test "generates module with specific group filter", %{
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "mikrotik.yaml"), """
      os: mikrotik
      group: mikrotik
      text: MikroTik
      """)

      File.write!(Path.join(profiles_dir, "cisco.yaml"), """
      os: cisco
      group: cisco
      text: Cisco
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run(["--group", "mikrotik"])
          end)

        assert output =~ "Generated 1 vendor modules"
        assert File.exists?(Path.join(vendors_dir, "mikrotik.ex"))
        refute File.exists?(Path.join(vendors_dir, "cisco.ex"))
      end)
    end

    test "handles vendor names starting with numbers", %{
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "4rf.yaml"), """
      os: 4rf
      group: 4rf
      text: 4RF
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run([])
          end)

        # Should prefix with "n" for filename and "N" for module
        assert output =~ "Generated: lib/towerops/snmp/profiles/vendors/n4rf.ex"

        vendor_file = Path.join(vendors_dir, "n4rf.ex")
        assert File.exists?(vendor_file)

        content = File.read!(vendor_file)
        assert content =~ "defmodule Towerops.Snmp.Profiles.Vendors.N4rf do"
      end)
    end

    test "handles vendor names with special characters", %{
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "device.yaml"), """
      os: test-device
      group: hp-aruba
      text: HP Aruba
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run([])
          end)

        # Should convert to snake_case for filename
        assert output =~ "Generated: lib/towerops/snmp/profiles/vendors/hp_aruba.ex"

        vendor_file = Path.join(vendors_dir, "hp_aruba.ex")
        assert File.exists?(vendor_file)

        content = File.read!(vendor_file)
        assert content =~ "defmodule Towerops.Snmp.Profiles.Vendors.HpAruba do"
      end)
    end

    test "extracts enterprise OID from sysObjectID", %{
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "test.yaml"), """
      os: test
      group: testvendor
      text: Test Vendor
      discovery:
        - sysObjectID:
            - .1.3.6.1.4.1.12345.1.2.3
      """)

      File.cd!(temp_dir, fn ->
        capture_io(fn ->
          GenVendorModules.run([])
        end)

        vendor_file = Path.join(vendors_dir, "testvendor.ex")
        content = File.read!(vendor_file)

        assert content =~ "# Enterprise OID: 1.3.6.1.4.1.12345"
      end)
    end

    test "handles profile without enterprise OID", %{
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "generic.yaml"), """
      os: generic
      group: generic
      text: Generic Device
      """)

      File.cd!(temp_dir, fn ->
        capture_io(fn ->
          GenVendorModules.run([])
        end)

        vendor_file = Path.join(vendors_dir, "generic.ex")
        content = File.read!(vendor_file)

        assert content =~ "# Enterprise OID: unknown - uses standard MIB OIDs"
      end)
    end

    test "includes standard CPU sensor in wireless_oid_defs", %{
      profiles_dir: profiles_dir,
      vendors_dir: vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "test.yaml"), """
      os: test
      group: test
      text: Test
      """)

      File.cd!(temp_dir, fn ->
        capture_io(fn ->
          GenVendorModules.run([])
        end)

        vendor_file = Path.join(vendors_dir, "test.ex")
        content = File.read!(vendor_file)

        assert content =~ "wireless_oid_defs do"
        assert content =~ "1.3.6.1.2.1.25.3.3.1.2.1"
        assert content =~ "CPU Load"
        assert content =~ "sensor_type: \"load\""
      end)
    end

    test "skips profiles that cannot be grouped to a vendor", %{
      profiles_dir: profiles_dir,
      vendors_dir: _vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "unknown_device.yaml"), """
      os: unknown-device
      text: Unknown Device
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run([])
          end)

        assert output =~ "0 new vendors to generate"
        assert output =~ "No new vendors to generate!"
      end)
    end

    test "includes instructions after generation", %{
      profiles_dir: profiles_dir,
      vendors_dir: _vendors_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(profiles_dir, "test.yaml"), """
      os: test
      group: test
      text: Test
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run([])
          end)

        assert output =~ "Run `mix compile` to check for errors"
        assert output =~ "Run `mix test` to verify"
        assert output =~ "Add aliases to registry.ex"
        assert output =~ "Add vendors to @vendors list in registry.ex"
        assert output =~ "Write tests for each vendor"
        assert output =~ "Add vendor-specific OIDs if available"
      end)
    end
  end

  describe "run/1 error handling" do
    test "skips profiles with invalid YAML", %{profiles_dir: profiles_dir, vendors_dir: vendors_dir, temp_dir: temp_dir} do
      File.write!(Path.join(profiles_dir, "valid.yaml"), """
      os: valid
      group: valid
      text: Valid
      """)

      File.write!(Path.join(profiles_dir, "invalid.yaml"), """
      this is: [invalid yaml that won't parse
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run([])
          end)

        # Should still generate valid vendor
        assert output =~ "Generated 1 vendor modules"
        assert File.exists?(Path.join(vendors_dir, "valid.ex"))
      end)
    end

    test "skips profiles without os field", %{profiles_dir: profiles_dir, vendors_dir: _vendors_dir, temp_dir: temp_dir} do
      File.write!(Path.join(profiles_dir, "no_os.yaml"), """
      group: test
      text: Test
      """)

      File.cd!(temp_dir, fn ->
        output =
          capture_io(fn ->
            GenVendorModules.run([])
          end)

        assert output =~ "0 new vendors to generate"
      end)
    end
  end
end
