defmodule Towerops.Agent.ProtoTest do
  @moduledoc """
  Tests for Protocol Buffer generated modules.

  These tests verify that encoding and decoding works correctly for all
  protobuf message types used in agent communication.
  """
  use ExUnit.Case, async: true

  alias Towerops.Agent.AgentError
  alias Towerops.Agent.AgentHeartbeat
  alias Towerops.Agent.AgentJob
  alias Towerops.Agent.AgentJobList
  alias Towerops.Agent.MonitoringCheck
  alias Towerops.Agent.NeighborDiscovery
  alias Towerops.Agent.Sensor
  alias Towerops.Agent.SnmpDevice
  alias Towerops.Agent.SnmpQuery
  alias Towerops.Agent.SnmpResult

  describe "AgentError" do
    test "encodes and decodes correctly" do
      error = %AgentError{
        device_id: "device-123",
        job_id: "job-456",
        message: "SNMP timeout",
        timestamp: 1_234_567_890
      }

      encoded = AgentError.encode(error)
      decoded = AgentError.decode(encoded)

      assert decoded.device_id == error.device_id
      assert decoded.job_id == error.job_id
      assert decoded.message == error.message
      assert decoded.timestamp == error.timestamp
    end

    test "encodes empty message" do
      error = %AgentError{}
      encoded = AgentError.encode(error)
      decoded = AgentError.decode(encoded)

      assert decoded.device_id == ""
      assert decoded.job_id == ""
      assert decoded.message == ""
      assert decoded.timestamp == 0
    end
  end

  describe "AgentHeartbeat" do
    test "encodes and decodes correctly" do
      heartbeat = %AgentHeartbeat{
        version: "1.0.0",
        hostname: "agent-host",
        uptime_seconds: 3600,
        ip_address: "192.168.1.100"
      }

      encoded = AgentHeartbeat.encode(heartbeat)
      decoded = AgentHeartbeat.decode(encoded)

      assert decoded.version == heartbeat.version
      assert decoded.hostname == heartbeat.hostname
      assert decoded.uptime_seconds == heartbeat.uptime_seconds
      assert decoded.ip_address == heartbeat.ip_address
    end

    test "encodes empty message" do
      heartbeat = %AgentHeartbeat{}
      encoded = AgentHeartbeat.encode(heartbeat)
      decoded = AgentHeartbeat.decode(encoded)

      assert decoded.version == ""
      assert decoded.hostname == ""
      assert decoded.uptime_seconds == 0
      assert decoded.ip_address == ""
    end
  end

  describe "JobType enum" do
    test "encodes DISCOVER constant" do
      # Enums are represented as atoms in structs but integers in the wire format
      job = %AgentJob{job_type: :DISCOVER}
      encoded = AgentJob.encode(job)
      decoded = AgentJob.decode(encoded)
      assert decoded.job_type == :DISCOVER
    end

    test "encodes POLL constant" do
      job = %AgentJob{job_type: :POLL}
      encoded = AgentJob.encode(job)
      decoded = AgentJob.decode(encoded)
      assert decoded.job_type == :POLL
    end
  end

  describe "QueryType enum" do
    test "encodes GET constant" do
      query = %SnmpQuery{query_type: :GET}
      encoded = SnmpQuery.encode(query)
      decoded = SnmpQuery.decode(encoded)
      assert decoded.query_type == :GET
    end

    test "encodes WALK constant" do
      query = %SnmpQuery{query_type: :WALK}
      encoded = SnmpQuery.encode(query)
      decoded = SnmpQuery.decode(encoded)
      assert decoded.query_type == :WALK
    end
  end

  describe "SnmpDevice" do
    test "encodes and decodes correctly" do
      device = %SnmpDevice{
        ip: "192.168.1.1",
        community: "public",
        version: "2c",
        port: 161
      }

      encoded = SnmpDevice.encode(device)
      decoded = SnmpDevice.decode(encoded)

      assert decoded.ip == device.ip
      assert decoded.community == device.community
      assert decoded.version == device.version
      assert decoded.port == device.port
    end

    test "encodes empty message" do
      device = %SnmpDevice{}
      encoded = SnmpDevice.encode(device)
      decoded = SnmpDevice.decode(encoded)

      assert decoded.ip == ""
      assert decoded.community == ""
      assert decoded.version == ""
      assert decoded.port == 0
    end
  end

  describe "SnmpQuery" do
    test "encodes and decodes GET query" do
      query = %SnmpQuery{
        query_type: :GET,
        oids: ["1.3.6.1.2.1.1.1.0", "1.3.6.1.2.1.1.2.0"]
      }

      encoded = SnmpQuery.encode(query)
      decoded = SnmpQuery.decode(encoded)

      assert decoded.query_type == :GET
      assert decoded.oids == query.oids
    end

    test "encodes and decodes WALK query" do
      query = %SnmpQuery{
        query_type: :WALK,
        oids: ["1.3.6.1.2.1.2.2.1"]
      }

      encoded = SnmpQuery.encode(query)
      decoded = SnmpQuery.decode(encoded)

      assert decoded.query_type == :WALK
      assert decoded.oids == query.oids
    end

    test "encodes empty message" do
      query = %SnmpQuery{}
      encoded = SnmpQuery.encode(query)
      decoded = SnmpQuery.decode(encoded)

      assert decoded.query_type == :GET
      assert decoded.oids == []
    end
  end

  describe "AgentJob" do
    test "encodes and decodes DISCOVER job" do
      job = %AgentJob{
        job_id: "discover:device-123",
        job_type: :DISCOVER,
        device_id: "device-123",
        snmp_device: %SnmpDevice{
          ip: "192.168.1.1",
          community: "public",
          version: "2c",
          port: 161
        },
        queries: [
          %SnmpQuery{
            query_type: :GET,
            oids: ["1.3.6.1.2.1.1.1.0"]
          }
        ]
      }

      encoded = AgentJob.encode(job)
      decoded = AgentJob.decode(encoded)

      assert decoded.job_id == job.job_id
      assert decoded.job_type == :DISCOVER
      assert decoded.device_id == job.device_id
      assert decoded.snmp_device.ip == job.snmp_device.ip
      assert decoded.snmp_device.community == job.snmp_device.community
      assert decoded.snmp_device.version == job.snmp_device.version
      assert decoded.snmp_device.port == job.snmp_device.port

      assert length(decoded.queries) == 1
      assert hd(decoded.queries).query_type == :GET
      assert hd(decoded.queries).oids == ["1.3.6.1.2.1.1.1.0"]
    end

    test "encodes and decodes POLL job" do
      job = %AgentJob{
        job_id: "poll:device-123",
        job_type: :POLL,
        device_id: "device-123",
        snmp_device: %SnmpDevice{
          ip: "192.168.1.1",
          community: "public",
          version: "2c",
          port: 161
        },
        queries: [
          %SnmpQuery{
            query_type: :GET,
            oids: ["1.3.6.1.4.1.14988.1.1.3.8.0"]
          }
        ]
      }

      encoded = AgentJob.encode(job)
      decoded = AgentJob.decode(encoded)

      assert decoded.job_id == job.job_id
      assert decoded.job_type == :POLL
      assert decoded.device_id == job.device_id
      assert decoded.snmp_device.ip == job.snmp_device.ip
      assert length(decoded.queries) == 1
    end

    test "encodes empty message" do
      job = %AgentJob{}
      encoded = AgentJob.encode(job)
      decoded = AgentJob.decode(encoded)

      assert decoded.job_id == ""
      assert decoded.job_type == :DISCOVER
      assert decoded.device_id == ""
      assert decoded.snmp_device == nil
      assert decoded.queries == []
    end
  end

  describe "AgentJobList" do
    test "encodes and decodes list of jobs" do
      job_list = %AgentJobList{
        jobs: [
          %AgentJob{
            job_id: "poll:device-1",
            job_type: :POLL,
            device_id: "device-1",
            snmp_device: %SnmpDevice{ip: "192.168.1.1"},
            queries: []
          },
          %AgentJob{
            job_id: "discover:device-2",
            job_type: :DISCOVER,
            device_id: "device-2",
            snmp_device: %SnmpDevice{ip: "192.168.1.2"},
            queries: []
          }
        ]
      }

      encoded = AgentJobList.encode(job_list)
      decoded = AgentJobList.decode(encoded)

      assert length(decoded.jobs) == 2
      assert Enum.at(decoded.jobs, 0).job_id == "poll:device-1"
      assert Enum.at(decoded.jobs, 0).job_type == :POLL
      assert Enum.at(decoded.jobs, 1).job_id == "discover:device-2"
      assert Enum.at(decoded.jobs, 1).job_type == :DISCOVER
    end

    test "encodes empty job list" do
      job_list = %AgentJobList{jobs: []}
      encoded = AgentJobList.encode(job_list)
      decoded = AgentJobList.decode(encoded)

      assert decoded.jobs == []
    end
  end

  describe "SnmpResult" do
    test "encodes and decodes with oid_values map" do
      result = %SnmpResult{
        device_id: "device-123",
        job_type: :POLL,
        oid_values: %{
          "1.3.6.1.2.1.1.1.0" => "Cisco IOS",
          "1.3.6.1.2.1.1.3.0" => "12345"
        },
        timestamp: 1_234_567_890
      }

      encoded = SnmpResult.encode(result)
      decoded = SnmpResult.decode(encoded)

      assert decoded.device_id == result.device_id
      assert decoded.job_type == :POLL
      assert decoded.oid_values == result.oid_values
      assert decoded.timestamp == result.timestamp
    end

    test "encodes and decodes DISCOVER result" do
      result = %SnmpResult{
        device_id: "device-123",
        job_type: :DISCOVER,
        oid_values: %{},
        timestamp: 1_234_567_890
      }

      encoded = SnmpResult.encode(result)
      decoded = SnmpResult.decode(encoded)

      assert decoded.device_id == result.device_id
      assert decoded.job_type == :DISCOVER
      assert decoded.timestamp == result.timestamp
    end

    test "encodes empty message" do
      result = %SnmpResult{}
      encoded = SnmpResult.encode(result)
      decoded = SnmpResult.decode(encoded)

      assert decoded.device_id == ""
      assert decoded.job_type == :DISCOVER
      assert decoded.oid_values == %{}
      assert decoded.timestamp == 0
    end
  end

  describe "SnmpResult.OidValuesEntry" do
    test "encodes and decodes map entry" do
      entry = %SnmpResult.OidValuesEntry{
        key: "1.3.6.1.2.1.1.1.0",
        value: "System Description"
      }

      encoded = SnmpResult.OidValuesEntry.encode(entry)
      decoded = SnmpResult.OidValuesEntry.decode(encoded)

      assert decoded.key == entry.key
      assert decoded.value == entry.value
    end
  end

  describe "MonitoringCheck" do
    test "encodes and decodes success check" do
      check = %MonitoringCheck{
        device_id: "device-123",
        status: "success",
        response_time_ms: 45.2,
        timestamp: 1_234_567_890
      }

      encoded = MonitoringCheck.encode(check)
      decoded = MonitoringCheck.decode(encoded)

      assert decoded.device_id == check.device_id
      assert decoded.status == check.status
      assert decoded.response_time_ms == check.response_time_ms
      assert decoded.timestamp == check.timestamp
    end

    test "encodes and decodes failure check" do
      check = %MonitoringCheck{
        device_id: "device-123",
        status: "failure",
        response_time_ms: 0.0,
        timestamp: 1_234_567_890
      }

      encoded = MonitoringCheck.encode(check)
      decoded = MonitoringCheck.decode(encoded)

      assert decoded.device_id == check.device_id
      assert decoded.status == check.status
      assert decoded.response_time_ms == check.response_time_ms
      assert decoded.timestamp == check.timestamp
    end

    test "encodes empty message" do
      check = %MonitoringCheck{}
      encoded = MonitoringCheck.encode(check)
      decoded = MonitoringCheck.decode(encoded)

      assert decoded.device_id == ""
      assert decoded.status == ""
      assert decoded.response_time_ms == 0.0
      assert decoded.timestamp == 0
    end
  end

  describe "NeighborDiscovery" do
    test "encodes and decodes LLDP neighbor" do
      neighbor = %NeighborDiscovery{
        interface_id: "interface-123",
        protocol: "LLDP",
        remote_chassis_id: "00:11:22:33:44:55",
        remote_system_name: "neighbor-switch",
        remote_system_description: "Cisco IOS",
        remote_platform: "cisco",
        remote_port_id: "GigabitEthernet1/0/1",
        remote_port_description: "Uplink to Core",
        remote_address: "192.168.1.2",
        remote_capabilities: ["bridge", "router"],
        timestamp: 1_234_567_890
      }

      encoded = NeighborDiscovery.encode(neighbor)
      decoded = NeighborDiscovery.decode(encoded)

      assert decoded.interface_id == neighbor.interface_id
      assert decoded.protocol == neighbor.protocol
      assert decoded.remote_chassis_id == neighbor.remote_chassis_id
      assert decoded.remote_system_name == neighbor.remote_system_name
      assert decoded.remote_system_description == neighbor.remote_system_description
      assert decoded.remote_platform == neighbor.remote_platform
      assert decoded.remote_port_id == neighbor.remote_port_id
      assert decoded.remote_port_description == neighbor.remote_port_description
      assert decoded.remote_address == neighbor.remote_address
      assert decoded.remote_capabilities == neighbor.remote_capabilities
      assert decoded.timestamp == neighbor.timestamp
    end

    test "encodes and decodes CDP neighbor" do
      neighbor = %NeighborDiscovery{
        interface_id: "interface-456",
        protocol: "CDP",
        remote_chassis_id: "Router-2",
        remote_system_name: "core-router",
        remote_platform: "Cisco 2960",
        remote_port_id: "FastEthernet0/1",
        timestamp: 1_234_567_890
      }

      encoded = NeighborDiscovery.encode(neighbor)
      decoded = NeighborDiscovery.decode(encoded)

      assert decoded.interface_id == neighbor.interface_id
      assert decoded.protocol == neighbor.protocol
      assert decoded.remote_chassis_id == neighbor.remote_chassis_id
      assert decoded.timestamp == neighbor.timestamp
    end

    test "encodes empty message" do
      neighbor = %NeighborDiscovery{}
      encoded = NeighborDiscovery.encode(neighbor)
      decoded = NeighborDiscovery.decode(encoded)

      assert decoded.interface_id == ""
      assert decoded.protocol == ""
      assert decoded.remote_chassis_id == ""
      assert decoded.remote_system_name == ""
      assert decoded.remote_capabilities == []
      assert decoded.timestamp == 0
    end
  end

  describe "Sensor.MetadataEntry" do
    test "encodes and decodes map entry" do
      entry = %Sensor.MetadataEntry{
        key: "location",
        value: "CPU Core 1"
      }

      encoded = Sensor.MetadataEntry.encode(entry)
      decoded = Sensor.MetadataEntry.decode(encoded)

      assert decoded.key == entry.key
      assert decoded.value == entry.value
    end

    test "encodes empty entry" do
      entry = %Sensor.MetadataEntry{}
      encoded = Sensor.MetadataEntry.encode(entry)
      decoded = Sensor.MetadataEntry.decode(encoded)

      assert decoded.key == ""
      assert decoded.value == ""
    end
  end

  describe "Sensor (protobuf)" do
    test "encodes and decodes sensor with metadata" do
      sensor = %Sensor{
        id: "sensor-123",
        type: "temperature",
        oid: "1.3.6.1.4.1.14988.1.1.3.10.0",
        divisor: 10.0,
        unit: "celsius",
        metadata: %{
          "location" => "CPU Core 1",
          "threshold" => "80"
        }
      }

      encoded = Sensor.encode(sensor)
      decoded = Sensor.decode(encoded)

      assert decoded.id == sensor.id
      assert decoded.type == sensor.type
      assert decoded.oid == sensor.oid
      assert decoded.divisor == sensor.divisor
      assert decoded.unit == sensor.unit
      assert decoded.metadata == sensor.metadata
    end

    test "encodes and decodes sensor without metadata" do
      sensor = %Sensor{
        id: "sensor-456",
        type: "voltage",
        oid: "1.3.6.1.4.1.14988.1.1.3.8.0",
        divisor: 10.0,
        unit: "volts"
      }

      encoded = Sensor.encode(sensor)
      decoded = Sensor.decode(encoded)

      assert decoded.id == sensor.id
      assert decoded.type == sensor.type
      assert decoded.oid == sensor.oid
      assert decoded.divisor == sensor.divisor
      assert decoded.unit == sensor.unit
    end

    test "encodes empty message" do
      sensor = %Sensor{}
      encoded = Sensor.encode(sensor)
      decoded = Sensor.decode(encoded)

      assert decoded.id == ""
      assert decoded.type == ""
      assert decoded.oid == ""
      assert decoded.divisor == 0.0
      assert decoded.unit == ""
      assert decoded.metadata == %{}
    end
  end
end
