defmodule Towerops.ProfilesTest do
  use Towerops.DataCase, async: true

  alias Towerops.Profiles
  alias Towerops.Profiles.DeviceProfile

  describe "list_profiles/0" do
    test "returns all enabled profiles ordered by priority" do
      {:ok, profile1} =
        Profiles.create_profile(%{
          name: "High Priority",
          vendor: "Cisco",
          priority: 10,
          enabled: true
        })

      {:ok, profile2} =
        Profiles.create_profile(%{
          name: "Low Priority",
          vendor: "MikroTik",
          priority: 100,
          enabled: true
        })

      {:ok, _profile3} =
        Profiles.create_profile(%{
          name: "Disabled",
          vendor: "Ubiquiti",
          priority: 50,
          enabled: false
        })

      profiles = Profiles.list_profiles()

      assert length(profiles) == 2
      assert hd(profiles).id == profile1.id
      assert Enum.at(profiles, 1).id == profile2.id
    end

    test "returns empty list when no enabled profiles" do
      {:ok, _profile} =
        Profiles.create_profile(%{
          name: "Disabled",
          vendor: "Test",
          enabled: false
        })

      assert Profiles.list_profiles() == []
    end
  end

  describe "get_profile/1" do
    test "returns profile by name with associations" do
      {:ok, profile} =
        Profiles.create_profile(%{
          name: "Test Profile",
          vendor: "Test Vendor"
        })

      {:ok, _device_oid} =
        Profiles.add_device_oid(profile.id, "serial_number", "TEST-MIB::serialNumber.0")

      {:ok, _sensor_oid} =
        Profiles.add_sensor_oid(profile.id, %{
          sensor_type: "temperature",
          mib_name: "TEST-MIB::temperature.0"
        })

      retrieved = Profiles.get_profile("Test Profile")

      assert retrieved.id == profile.id
      assert length(retrieved.device_oids) == 1
      assert length(retrieved.sensor_oids) == 1
    end

    test "returns nil when profile doesn't exist" do
      assert Profiles.get_profile("Nonexistent") == nil
    end
  end

  describe "match_profile/1" do
    setup do
      {:ok, regex_profile} =
        Profiles.create_profile(%{
          name: "Regex Match",
          vendor: "Cisco",
          detection_pattern: "Cisco IOS",
          priority: 10
        })

      {:ok, oid_profile} =
        Profiles.create_profile(%{
          name: "OID Match",
          vendor: "MikroTik",
          detection_oid: "1.3.6.1.4.1.14988",
          priority: 20
        })

      {:ok, both_profile} =
        Profiles.create_profile(%{
          name: "Both Match",
          vendor: "Ubiquiti",
          detection_pattern: "EdgeRouter",
          detection_oid: "1.3.6.1.4.1.41112",
          priority: 30
        })

      %{
        regex_profile: regex_profile,
        oid_profile: oid_profile,
        both_profile: both_profile
      }
    end

    test "matches by detection_pattern regex", %{regex_profile: profile} do
      system_info = %{
        sys_descr: "Cisco IOS Software, Version 15.2",
        sys_object_id: "1.3.6.1.4.1.9"
      }

      assert Profiles.match_profile(system_info).id == profile.id
    end

    test "matches by detection_oid prefix", %{oid_profile: profile} do
      system_info = %{
        sys_descr: "Some description",
        sys_object_id: "1.3.6.1.4.1.14988.1.1"
      }

      assert Profiles.match_profile(system_info).id == profile.id
    end

    test "matches higher priority profile first", %{regex_profile: profile} do
      system_info = %{
        sys_descr: "Cisco IOS Software",
        sys_object_id: "1.3.6.1.4.1.14988"
      }

      # Both profiles match, but regex_profile has higher priority (10 < 20)
      assert Profiles.match_profile(system_info).id == profile.id
    end

    test "returns nil when no profile matches" do
      system_info = %{
        sys_descr: "Unknown Device",
        sys_object_id: "1.2.3.4.5"
      }

      assert Profiles.match_profile(system_info) == nil
    end

    test "handles missing sys_descr" do
      {:ok, profile} =
        Profiles.create_profile(%{
          name: "OID Only",
          vendor: "Test",
          detection_oid: "1.2.3.4"
        })

      system_info = %{sys_object_id: "1.2.3.4.5"}

      assert Profiles.match_profile(system_info).id == profile.id
    end

    test "handles missing sys_object_id" do
      {:ok, profile} =
        Profiles.create_profile(%{
          name: "Regex Only",
          vendor: "Test",
          detection_pattern: "Test Device"
        })

      system_info = %{sys_descr: "Test Device v1.0"}

      assert Profiles.match_profile(system_info).id == profile.id
    end

    test "handles invalid regex pattern gracefully" do
      {:ok, _profile} =
        Profiles.create_profile(%{
          name: "Invalid Regex",
          vendor: "Test",
          detection_pattern: "[invalid(regex"
        })

      system_info = %{sys_descr: "Some description"}

      # Should not crash, just not match
      assert Profiles.match_profile(system_info) == nil
    end
  end

  describe "create_profile/1" do
    test "creates profile with valid attributes" do
      attrs = %{
        name: "New Profile",
        vendor: "Test Vendor",
        detection_pattern: "Test.*Device",
        detection_oid: "1.3.6.1.4.1.12345",
        priority: 50,
        enabled: true
      }

      assert {:ok, %DeviceProfile{} = profile} = Profiles.create_profile(attrs)
      assert profile.name == "New Profile"
      assert profile.vendor == "Test Vendor"
      assert profile.detection_pattern == "Test.*Device"
      assert profile.detection_oid == "1.3.6.1.4.1.12345"
      assert profile.priority == 50
      assert profile.enabled == true
    end

    test "creates profile with minimal attributes" do
      attrs = %{
        name: "Minimal Profile",
        vendor: "Test"
      }

      assert {:ok, %DeviceProfile{} = profile} = Profiles.create_profile(attrs)
      assert profile.name == "Minimal Profile"
      assert profile.vendor == "Test"
      assert profile.priority == 100
      assert profile.enabled == true
    end

    test "returns error with missing required fields" do
      assert {:error, changeset} = Profiles.create_profile(%{})
      assert "can't be blank" in errors_on(changeset).name
      assert "can't be blank" in errors_on(changeset).vendor
    end

    test "returns error with duplicate name" do
      {:ok, _profile} = Profiles.create_profile(%{name: "Duplicate", vendor: "Test"})

      assert {:error, changeset} = Profiles.create_profile(%{name: "Duplicate", vendor: "Test"})
      assert "has already been taken" in errors_on(changeset).name
    end
  end

  describe "update_profile/2" do
    test "updates profile with valid attributes" do
      {:ok, profile} = Profiles.create_profile(%{name: "Original", vendor: "Original Vendor"})

      assert {:ok, updated} =
               Profiles.update_profile(profile, %{
                 name: "Updated",
                 vendor: "Updated Vendor",
                 priority: 25
               })

      assert updated.name == "Updated"
      assert updated.vendor == "Updated Vendor"
      assert updated.priority == 25
    end

    test "returns error with invalid attributes" do
      {:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})

      assert {:error, changeset} = Profiles.update_profile(profile, %{name: nil})
      assert "can't be blank" in errors_on(changeset).name
    end
  end

  describe "delete_profile/1" do
    test "deletes profile" do
      {:ok, profile} = Profiles.create_profile(%{name: "To Delete", vendor: "Test"})

      assert {:ok, %DeviceProfile{}} = Profiles.delete_profile(profile)
      assert Profiles.get_profile("To Delete") == nil
    end
  end

  describe "add_device_oid/3" do
    test "adds device OID to profile" do
      {:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})

      assert {:ok, device_oid} =
               Profiles.add_device_oid(
                 profile.id,
                 "serial_number",
                 "TEST-MIB::serialNumber.0"
               )

      assert device_oid.field == "serial_number"
      assert device_oid.mib_name == "TEST-MIB::serialNumber.0"
      assert device_oid.profile_id == profile.id
    end

    test "returns error with duplicate field for same profile" do
      {:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})

      {:ok, _oid1} = Profiles.add_device_oid(profile.id, "serial_number", "MIB1::sn.0")

      assert {:error, changeset} =
               Profiles.add_device_oid(profile.id, "serial_number", "MIB2::sn.0")

      assert "has already been taken" in errors_on(changeset).profile_id
    end
  end

  describe "add_sensor_oid/2" do
    test "adds sensor OID to profile" do
      {:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})

      attrs = %{
        sensor_type: "temperature",
        mib_name: "TEST-MIB::temperature.0",
        sensor_descr: "CPU Temperature",
        sensor_unit: "celsius",
        sensor_divisor: 10
      }

      assert {:ok, sensor_oid} = Profiles.add_sensor_oid(profile.id, attrs)

      assert sensor_oid.sensor_type == "temperature"
      assert sensor_oid.mib_name == "TEST-MIB::temperature.0"
      assert sensor_oid.sensor_descr == "CPU Temperature"
      assert sensor_oid.sensor_unit == "celsius"
      assert sensor_oid.sensor_divisor == 10
      assert sensor_oid.profile_id == profile.id
    end

    test "adds sensor OID with minimal attributes" do
      {:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})

      attrs = %{
        sensor_type: "voltage",
        mib_name: "TEST-MIB::voltage.0"
      }

      assert {:ok, sensor_oid} = Profiles.add_sensor_oid(profile.id, attrs)

      assert sensor_oid.sensor_type == "voltage"
      assert sensor_oid.mib_name == "TEST-MIB::voltage.0"
      assert sensor_oid.sensor_divisor == 1
    end

    test "returns error with missing required fields" do
      {:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})

      assert {:error, changeset} = Profiles.add_sensor_oid(profile.id, %{})
      assert "can't be blank" in errors_on(changeset).sensor_type
      assert "can't be blank" in errors_on(changeset).mib_name
    end
  end
end
