defmodule Mix.Tasks.ImportProfilesTest do
  use Towerops.DataCase, async: true

  import ExUnit.CaptureIO

  alias Mix.Tasks.ImportProfiles
  alias Towerops.Profiles
  alias Towerops.Profiles.DeviceOid
  alias Towerops.Profiles.SensorOid
  alias Towerops.Repo

  setup do
    # Create temporary directory for test YAML files
    temp_dir = Path.join(System.tmp_dir!(), "profiles_test_#{System.unique_integer([:positive])}")
    detection_dir = Path.join([temp_dir, "os_detection"])
    discovery_dir = Path.join([temp_dir, "os_discovery"])
    File.mkdir_p!(detection_dir)
    File.mkdir_p!(discovery_dir)

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

    {:ok, temp_dir: temp_dir, detection_dir: detection_dir, discovery_dir: discovery_dir}
  end

  describe "run/1 with valid profiles" do
    test "imports a basic profile from YAML", %{
      detection_dir: detection_dir,
      discovery_dir: discovery_dir,
      temp_dir: temp_dir
    } do
      # Create a simple detection file
      detection_yaml = Path.join(detection_dir, "mikrotik.yaml")

      File.write!(detection_yaml, """
      os: mikrotik
      text: MikroTik
      priority: 100
      discovery:
        - sysDescr:
            - RouterOS
        - sysObjectID:
            - .1.3.6.1.4.1.14988.
      """)

      # Create a simple discovery file
      discovery_yaml = Path.join(discovery_dir, "mikrotik.yaml")

      File.write!(discovery_yaml, """
      modules:
        os:
          serial: SNMPv2-MIB::sysContact.0
          version: SNMPv2-MIB::sysName.0
        sensors:
          temperature:
            data:
              - oid: MIKROTIK-MIB::mtxrHlTemperature.0
                descr: System Temperature
                unit: C
                divisor: 10
      """)

      output =
        capture_io(fn ->
          ImportProfiles.run(["--source-path", temp_dir])
        end)

      assert output =~ "✓ mikrotik: created/updated successfully"
      assert output =~ "Import complete: 1 succeeded, 0 failed"

      # Verify profile was created
      profile = Profiles.get_profile("mikrotik")
      assert profile
      assert profile.vendor == "MikroTik"
      assert profile.priority == 100
      assert profile.enabled == true

      # Verify device OIDs were created
      device_oids = Repo.all(from o in DeviceOid, where: o.profile_id == ^profile.id)
      assert length(device_oids) == 2

      serial_oid = Enum.find(device_oids, &(&1.field == "serial_number"))
      assert serial_oid.mib_name == "SNMPv2-MIB::sysContact.0"

      version_oid = Enum.find(device_oids, &(&1.field == "firmware_version"))
      assert version_oid.mib_name == "SNMPv2-MIB::sysName.0"

      # Verify sensor OIDs were created
      sensor_oids = Repo.all(from o in SensorOid, where: o.profile_id == ^profile.id)
      assert length(sensor_oids) == 1

      temp_sensor = List.first(sensor_oids)
      assert temp_sensor.sensor_type == "temperature"
      assert temp_sensor.mib_name == "MIKROTIK-MIB::mtxrHlTemperature.0"
      assert temp_sensor.sensor_descr == "System Temperature"
      assert temp_sensor.sensor_unit == "C"
      assert temp_sensor.sensor_divisor == 10
    end

    test "updates existing profile", %{detection_dir: detection_dir, temp_dir: temp_dir} do
      # Create initial profile
      {:ok, profile} =
        Profiles.create_profile(%{
          name: "cisco",
          vendor: "Cisco Old",
          detection_pattern: "old pattern",
          priority: 50,
          enabled: true
        })

      # Create YAML file with updated info
      detection_yaml = Path.join(detection_dir, "cisco.yaml")

      File.write!(detection_yaml, """
      os: cisco
      text: Cisco Updated
      priority: 200
      discovery:
        - sysDescr:
            - Cisco IOS
      """)

      output =
        capture_io(fn ->
          ImportProfiles.run(["--source-path", temp_dir])
        end)

      assert output =~ "✓ cisco: created/updated successfully"

      # Verify profile was updated
      updated_profile = Profiles.get_profile("cisco")
      assert updated_profile.id == profile.id
      assert updated_profile.vendor == "Cisco Updated"
      assert updated_profile.priority == 200
    end

    test "imports specific profiles only", %{detection_dir: detection_dir, temp_dir: temp_dir} do
      # Create multiple detection files
      File.write!(Path.join(detection_dir, "mikrotik.yaml"), """
      os: mikrotik
      text: MikroTik
      priority: 100
      """)

      File.write!(Path.join(detection_dir, "cisco.yaml"), """
      os: cisco
      text: Cisco
      priority: 100
      """)

      File.write!(Path.join(detection_dir, "ubiquiti.yaml"), """
      os: ubiquiti
      text: Ubiquiti
      priority: 100
      """)

      output =
        capture_io(fn ->
          ImportProfiles.run(["--source-path", temp_dir, "--profiles", "mikrotik,cisco"])
        end)

      assert output =~ "Importing 2 specified profiles"
      assert output =~ "✓ mikrotik"
      assert output =~ "✓ cisco"
      refute output =~ "ubiquiti"

      # Verify only specified profiles were created
      assert Profiles.get_profile("mikrotik")
      assert Profiles.get_profile("cisco")
      assert Profiles.get_profile("ubiquiti") == nil
    end

    test "handles profile without discovery file", %{detection_dir: detection_dir, temp_dir: temp_dir} do
      detection_yaml = Path.join(detection_dir, "generic.yaml")

      File.write!(detection_yaml, """
      os: generic
      text: Generic Device
      priority: 1
      """)

      output =
        capture_io(fn ->
          ImportProfiles.run(["--source-path", temp_dir])
        end)

      assert output =~ "✓ generic: created/updated successfully"

      profile = Profiles.get_profile("generic")
      assert profile
      assert profile.vendor == "Generic Device"

      # Should have no device OIDs or sensor OIDs
      assert Repo.aggregate(from(o in DeviceOid, where: o.profile_id == ^profile.id), :count) == 0
      assert Repo.aggregate(from(o in SensorOid, where: o.profile_id == ^profile.id), :count) == 0
    end

    test "extracts detection patterns from sysDescr", %{detection_dir: detection_dir, temp_dir: temp_dir} do
      detection_yaml = Path.join(detection_dir, "test.yaml")

      File.write!(detection_yaml, """
      os: test
      text: Test Device
      discovery:
        - sysDescr:
            - "Test Pattern 1"
            - "Test Pattern 2"
      """)

      capture_io(fn ->
        ImportProfiles.run(["--source-path", temp_dir])
      end)

      profile = Profiles.get_profile("test")
      assert profile.detection_pattern == "Test Pattern 1"
    end

    test "extracts detection OID from sysObjectID", %{detection_dir: detection_dir, temp_dir: temp_dir} do
      detection_yaml = Path.join(detection_dir, "test.yaml")

      File.write!(detection_yaml, """
      os: test
      text: Test Device
      discovery:
        - sysObjectID:
            - ".1.3.6.1.4.1.9."
            - ".1.3.6.1.4.1.10."
      """)

      capture_io(fn ->
        ImportProfiles.run(["--source-path", temp_dir])
      end)

      profile = Profiles.get_profile("test")
      assert profile.detection_oid == ".1.3.6.1.4.1.9."
    end

    test "imports all standard device OID fields", %{
      detection_dir: detection_dir,
      discovery_dir: discovery_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(detection_dir, "test.yaml"), """
      os: test
      text: Test Device
      """)

      File.write!(Path.join(discovery_dir, "test.yaml"), """
      modules:
        os:
          serial: DEVICE-MIB::serialNumber.0
          version: DEVICE-MIB::firmwareVersion.0
          hardware: DEVICE-MIB::hardwareModel.0
          lat: DEVICE-MIB::latitude.0
          long: DEVICE-MIB::longitude.0
          features: DEVICE-MIB::features.0
          sysName: SNMPv2-MIB::sysName.0
      """)

      capture_io(fn ->
        ImportProfiles.run(["--source-path", temp_dir])
      end)

      profile = Profiles.get_profile("test")
      device_oids = Repo.all(from o in DeviceOid, where: o.profile_id == ^profile.id)

      assert length(device_oids) == 7

      assert Enum.find(device_oids, &(&1.field == "serial_number")).mib_name == "DEVICE-MIB::serialNumber.0"
      assert Enum.find(device_oids, &(&1.field == "firmware_version")).mib_name == "DEVICE-MIB::firmwareVersion.0"
      assert Enum.find(device_oids, &(&1.field == "hardware")).mib_name == "DEVICE-MIB::hardwareModel.0"
      assert Enum.find(device_oids, &(&1.field == "latitude")).mib_name == "DEVICE-MIB::latitude.0"
      assert Enum.find(device_oids, &(&1.field == "longitude")).mib_name == "DEVICE-MIB::longitude.0"
      assert Enum.find(device_oids, &(&1.field == "features")).mib_name == "DEVICE-MIB::features.0"
      assert Enum.find(device_oids, &(&1.field == "sys_name_oid")).mib_name == "SNMPv2-MIB::sysName.0"
    end

    test "imports all sensor types", %{detection_dir: detection_dir, discovery_dir: discovery_dir, temp_dir: temp_dir} do
      File.write!(Path.join(detection_dir, "test.yaml"), """
      os: test
      text: Test Device
      """)

      File.write!(Path.join(discovery_dir, "test.yaml"), """
      modules:
        sensors:
          temperature:
            data:
              - oid: DEVICE-MIB::temp.0
                descr: Temperature
                unit: C
          voltage:
            data:
              - oid: DEVICE-MIB::voltage.0
                descr: Voltage
                unit: V
          current:
            data:
              - oid: DEVICE-MIB::current.0
                descr: Current
                unit: A
          power:
            data:
              - oid: DEVICE-MIB::power.0
                descr: Power
                unit: W
          fanspeed:
            data:
              - oid: DEVICE-MIB::fan.0
                descr: Fan Speed
                unit: RPM
      """)

      capture_io(fn ->
        ImportProfiles.run(["--source-path", temp_dir])
      end)

      profile = Profiles.get_profile("test")
      sensor_oids = Repo.all(from o in SensorOid, where: o.profile_id == ^profile.id)

      assert length(sensor_oids) == 5

      assert Enum.any?(sensor_oids, &(&1.sensor_type == "temperature"))
      assert Enum.any?(sensor_oids, &(&1.sensor_type == "voltage"))
      assert Enum.any?(sensor_oids, &(&1.sensor_type == "current"))
      assert Enum.any?(sensor_oids, &(&1.sensor_type == "power"))
      assert Enum.any?(sensor_oids, &(&1.sensor_type == "fanspeed"))
    end

    test "skips table sensors with {{ $index }}", %{
      detection_dir: detection_dir,
      discovery_dir: discovery_dir,
      temp_dir: temp_dir
    } do
      File.write!(Path.join(detection_dir, "test.yaml"), """
      os: test
      text: Test Device
      """)

      File.write!(Path.join(discovery_dir, "test.yaml"), """
      modules:
        sensors:
          temperature:
            data:
              - oid: DEVICE-MIB::temp.0
                descr: Scalar Temperature
                unit: C
              - oid: DEVICE-MIB::tempTable
                num_oid: .1.3.6.1.4.1.9999.1.2.{{ $index }}
                descr: Table Temperature
                unit: C
      """)

      capture_io(fn ->
        ImportProfiles.run(["--source-path", temp_dir])
      end)

      profile = Profiles.get_profile("test")
      sensor_oids = Repo.all(from o in SensorOid, where: o.profile_id == ^profile.id)

      # Only scalar sensor should be imported
      assert length(sensor_oids) == 1
      assert List.first(sensor_oids).sensor_descr == "Scalar Temperature"
    end

    test "replaces existing OIDs when reimporting", %{
      detection_dir: detection_dir,
      discovery_dir: discovery_dir,
      temp_dir: temp_dir
    } do
      # Create initial profile with OIDs
      {:ok, profile} = Profiles.create_profile(%{name: "test", vendor: "Test", priority: 100, enabled: true})
      {:ok, _} = Profiles.add_device_oid(profile.id, "serial_number", "OLD-MIB::serial.0")

      {:ok, _} =
        Profiles.add_sensor_oid(profile.id, %{
          sensor_type: "temperature",
          mib_name: "OLD-MIB::temp.0",
          sensor_descr: "Old Temp",
          sensor_unit: "C",
          sensor_divisor: 1
        })

      # Create new YAML with different OIDs
      File.write!(Path.join(detection_dir, "test.yaml"), """
      os: test
      text: Test Device
      """)

      File.write!(Path.join(discovery_dir, "test.yaml"), """
      modules:
        os:
          serial: NEW-MIB::serialNumber.0
        sensors:
          voltage:
            data:
              - oid: NEW-MIB::voltage.0
                descr: New Voltage
                unit: V
      """)

      capture_io(fn ->
        ImportProfiles.run(["--source-path", temp_dir])
      end)

      # Old OIDs should be replaced
      device_oids = Repo.all(from o in DeviceOid, where: o.profile_id == ^profile.id)
      assert length(device_oids) == 1
      assert List.first(device_oids).mib_name == "NEW-MIB::serialNumber.0"

      sensor_oids = Repo.all(from o in SensorOid, where: o.profile_id == ^profile.id)
      assert length(sensor_oids) == 1
      assert List.first(sensor_oids).mib_name == "NEW-MIB::voltage.0"
      assert List.first(sensor_oids).sensor_type == "voltage"
    end
  end

  describe "run/1 error handling" do
    test "raises error when source directory doesn't exist" do
      assert_raise RuntimeError, "Source directory not found: /nonexistent/path", fn ->
        capture_io(fn ->
          ImportProfiles.run(["--source-path", "/nonexistent/path"])
        end)
      end
    end

    test "reports failed imports", %{detection_dir: detection_dir, temp_dir: temp_dir} do
      # Create invalid YAML
      File.write!(Path.join(detection_dir, "invalid.yaml"), """
      this is not: [valid yaml: that will parse
      """)

      output =
        capture_io([capture_prompt: false], fn ->
          ImportProfiles.run(["--source-path", temp_dir])
        end)

      assert output =~ "✗ invalid" or output =~ "Import complete: 0 succeeded, 1 failed"
      assert output =~ "Import complete: 0 succeeded, 1 failed"
    end

    test "uses default source path when not provided" do
      # This will fail because priv/profiles doesn't have test data,
      # but we can verify it tries to use the right path
      output =
        capture_io(fn ->
          ImportProfiles.run([])
        end)

      # Should try to load from default location
      assert output =~ "Discovering YAML files" or output =~ "Found 0 YAML files" or output =~ "succeeded"
    end
  end
end
