defmodule Towerops.Proto.AgentPbTest do
  use ExUnit.Case, async: true

  alias Towerops.Agent.AgentConfig
  alias Towerops.Agent.Device
  alias Towerops.Agent.HeartbeatMetadata
  alias Towerops.Agent.HeartbeatResponse
  alias Towerops.Agent.Interface
  alias Towerops.Agent.InterfaceStat
  alias Towerops.Agent.Metric
  alias Towerops.Agent.MetricBatch
  alias Towerops.Agent.Sensor
  alias Towerops.Agent.SensorReading
  alias Towerops.Agent.SnmpConfig

  describe "AgentConfig" do
    test "creates struct with default values" do
      config = %AgentConfig{}
      assert config.version == ""
      assert config.poll_interval_seconds == 0
      assert config.devices == []
    end

    test "creates struct with values" do
      device = %Device{id: "eq1", name: "Router"}

      config = %AgentConfig{
        version: "1.0.0",
        poll_interval_seconds: 60,
        devices: [device]
      }

      assert config.version == "1.0.0"
      assert config.poll_interval_seconds == 60
      assert length(config.devices) == 1
      assert hd(config.devices).id == "eq1"
    end

    test "encodes and decodes" do
      config = %AgentConfig{
        version: "1.0.0",
        poll_interval_seconds: 60
      }

      encoded = AgentConfig.encode(config)
      assert is_binary(encoded)

      decoded = AgentConfig.decode(encoded)
      assert decoded.version == "1.0.0"
      assert decoded.poll_interval_seconds == 60
    end
  end

  describe "Device" do
    test "creates struct with default values" do
      device = %Device{}
      assert device.id == ""
      assert device.name == ""
      assert device.ip_address == ""
      assert device.snmp == nil
      assert device.poll_interval_seconds == 0
      assert device.sensors == []
      assert device.interfaces == []
    end

    test "creates struct with SNMP config" do
      snmp = %SnmpConfig{
        enabled: true,
        version: "2c",
        community: "public",
        port: 161
      }

      device = %Device{
        id: "eq1",
        name: "Router 1",
        ip_address: "192.168.1.1",
        snmp: snmp,
        poll_interval_seconds: 60
      }

      assert device.id == "eq1"
      assert device.name == "Router 1"
      assert device.ip_address == "192.168.1.1"
      assert device.snmp.enabled == true
      assert device.poll_interval_seconds == 60
    end

    test "creates struct with sensors and interfaces" do
      sensor = %Sensor{id: "s1", type: "temperature", oid: "1.3.6.1.2.1"}
      interface = %Interface{id: "i1", if_index: 1, if_name: "eth0"}

      device = %Device{
        id: "eq1",
        sensors: [sensor],
        interfaces: [interface]
      }

      assert length(device.sensors) == 1
      assert length(device.interfaces) == 1
      assert hd(device.sensors).id == "s1"
      assert hd(device.interfaces).id == "i1"
    end

    test "encodes and decodes" do
      device = %Device{
        id: "eq1",
        name: "Router 1",
        ip_address: "192.168.1.1"
      }

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

      assert decoded.id == "eq1"
      assert decoded.name == "Router 1"
      assert decoded.ip_address == "192.168.1.1"
    end
  end

  describe "SnmpConfig" do
    test "creates struct with default values" do
      snmp = %SnmpConfig{}
      assert snmp.enabled == false
      assert snmp.version == ""
      assert snmp.community == ""
      assert snmp.port == 0
    end

    test "creates struct with values" do
      snmp = %SnmpConfig{
        enabled: true,
        version: "2c",
        community: "public",
        port: 161
      }

      assert snmp.enabled == true
      assert snmp.version == "2c"
      assert snmp.community == "public"
      assert snmp.port == 161
    end

    test "encodes and decodes" do
      snmp = %SnmpConfig{
        enabled: true,
        version: "2c",
        community: "public",
        port: 161
      }

      encoded = SnmpConfig.encode(snmp)
      decoded = SnmpConfig.decode(encoded)

      assert decoded.enabled == true
      assert decoded.version == "2c"
      assert decoded.community == "public"
      assert decoded.port == 161
    end
  end

  describe "Sensor" do
    test "creates struct with default values" do
      sensor = %Sensor{}
      assert sensor.id == ""
      assert sensor.type == ""
      assert sensor.oid == ""
      assert sensor.divisor == 0.0
      assert sensor.unit == ""
      assert sensor.metadata == %{}
    end

    test "creates struct with values" do
      sensor = %Sensor{
        id: "s1",
        type: "temperature",
        oid: "1.3.6.1.2.1.99.1.1.1.4.1",
        divisor: 10.0,
        unit: "°C",
        metadata: [%{key: "location", value: "CPU"}]
      }

      assert sensor.id == "s1"
      assert sensor.type == "temperature"
      assert sensor.oid == "1.3.6.1.2.1.99.1.1.1.4.1"
      assert sensor.divisor == 10.0
      assert sensor.unit == "°C"
      assert length(sensor.metadata) == 1
    end

    test "encodes and decodes" do
      sensor = %Sensor{
        id: "s1",
        type: "temperature",
        oid: "1.3.6.1.2.1",
        divisor: 10.0,
        unit: "°C"
      }

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

      assert decoded.id == "s1"
      assert decoded.type == "temperature"
      assert decoded.divisor == 10.0
    end
  end

  describe "Sensor.MetadataEntry" do
    test "creates struct with default values" do
      entry = %Sensor.MetadataEntry{}
      assert entry.key == ""
      assert entry.value == ""
    end

    test "creates struct with values" do
      entry = %Sensor.MetadataEntry{key: "location", value: "CPU"}
      assert entry.key == "location"
      assert entry.value == "CPU"
    end
  end

  describe "Interface" do
    test "creates struct with default values" do
      interface = %Interface{}
      assert interface.id == ""
      assert interface.if_index == 0
      assert interface.if_name == ""
    end

    test "creates struct with values" do
      interface = %Interface{
        id: "i1",
        if_index: 1,
        if_name: "eth0"
      }

      assert interface.id == "i1"
      assert interface.if_index == 1
      assert interface.if_name == "eth0"
    end

    test "encodes and decodes" do
      interface = %Interface{
        id: "i1",
        if_index: 1,
        if_name: "eth0"
      }

      encoded = Interface.encode(interface)
      decoded = Interface.decode(encoded)

      assert decoded.id == "i1"
      assert decoded.if_index == 1
      assert decoded.if_name == "eth0"
    end
  end

  describe "MetricBatch" do
    test "creates struct with default values" do
      batch = %MetricBatch{}
      assert batch.metrics == []
    end

    test "creates struct with metrics" do
      reading = %SensorReading{sensor_id: "s1", value: 45.5}
      metric = %Metric{metric_type: {:sensor_reading, reading}}
      batch = %MetricBatch{metrics: [metric]}

      assert length(batch.metrics) == 1
      assert {:sensor_reading, reading} = hd(batch.metrics).metric_type
      assert reading.sensor_id == "s1"
    end

    test "encodes and decodes" do
      reading = %SensorReading{sensor_id: "s1", value: 45.5}
      metric = %Metric{metric_type: {:sensor_reading, reading}}
      batch = %MetricBatch{metrics: [metric]}

      encoded = MetricBatch.encode(batch)
      decoded = MetricBatch.decode(encoded)

      assert length(decoded.metrics) == 1
    end
  end

  describe "Metric" do
    test "creates struct with sensor_reading" do
      reading = %SensorReading{
        sensor_id: "s1",
        value: 45.5,
        status: "ok",
        timestamp: 1_640_000_000
      }

      metric = %Metric{metric_type: {:sensor_reading, reading}}
      assert {:sensor_reading, reading} = metric.metric_type
      assert reading.sensor_id == "s1"
      assert reading.value == 45.5
    end

    test "creates struct with interface_stat" do
      stat = %InterfaceStat{
        interface_id: "i1",
        if_in_octets: 1000,
        if_out_octets: 2000,
        timestamp: 1_640_000_000
      }

      metric = %Metric{metric_type: {:interface_stat, stat}}
      assert {:interface_stat, stat} = metric.metric_type
      assert stat.interface_id == "i1"
    end

    test "encodes and decodes sensor_reading" do
      reading = %SensorReading{sensor_id: "s1", value: 45.5}
      metric = %Metric{metric_type: {:sensor_reading, reading}}

      encoded = Metric.encode(metric)
      decoded = Metric.decode(encoded)

      assert {:sensor_reading, decoded_reading} = decoded.metric_type
      assert decoded_reading.sensor_id == "s1"
    end

    test "encodes and decodes interface_stat" do
      stat = %InterfaceStat{interface_id: "i1", if_in_octets: 1000}
      metric = %Metric{metric_type: {:interface_stat, stat}}

      encoded = Metric.encode(metric)
      decoded = Metric.decode(encoded)

      assert {:interface_stat, decoded_stat} = decoded.metric_type
      assert decoded_stat.interface_id == "i1"
    end
  end

  describe "SensorReading" do
    test "creates struct with default values" do
      reading = %SensorReading{}
      assert reading.sensor_id == ""
      assert reading.value == 0.0
      assert reading.status == ""
      assert reading.timestamp == 0
    end

    test "creates struct with values" do
      reading = %SensorReading{
        sensor_id: "s1",
        value: 45.5,
        status: "ok",
        timestamp: 1_640_000_000
      }

      assert reading.sensor_id == "s1"
      assert reading.value == 45.5
      assert reading.status == "ok"
      assert reading.timestamp == 1_640_000_000
    end

    test "encodes and decodes" do
      reading = %SensorReading{
        sensor_id: "s1",
        value: 45.5,
        status: "ok",
        timestamp: 1_640_000_000
      }

      encoded = SensorReading.encode(reading)
      decoded = SensorReading.decode(encoded)

      assert decoded.sensor_id == "s1"
      assert decoded.value == 45.5
      assert decoded.status == "ok"
      assert decoded.timestamp == 1_640_000_000
    end
  end

  describe "InterfaceStat" do
    test "creates struct with default values" do
      stat = %InterfaceStat{}
      assert stat.interface_id == ""
      assert stat.if_in_octets == 0
      assert stat.if_out_octets == 0
      assert stat.if_in_errors == 0
      assert stat.if_out_errors == 0
      assert stat.if_in_discards == 0
      assert stat.if_out_discards == 0
      assert stat.timestamp == 0
    end

    test "creates struct with values" do
      stat = %InterfaceStat{
        interface_id: "i1",
        if_in_octets: 1000,
        if_out_octets: 2000,
        if_in_errors: 1,
        if_out_errors: 2,
        if_in_discards: 3,
        if_out_discards: 4,
        timestamp: 1_640_000_000
      }

      assert stat.interface_id == "i1"
      assert stat.if_in_octets == 1000
      assert stat.if_out_octets == 2000
      assert stat.if_in_errors == 1
      assert stat.if_out_errors == 2
      assert stat.if_in_discards == 3
      assert stat.if_out_discards == 4
      assert stat.timestamp == 1_640_000_000
    end

    test "encodes and decodes" do
      stat = %InterfaceStat{
        interface_id: "i1",
        if_in_octets: 1000,
        if_out_octets: 2000,
        timestamp: 1_640_000_000
      }

      encoded = InterfaceStat.encode(stat)
      decoded = InterfaceStat.decode(encoded)

      assert decoded.interface_id == "i1"
      assert decoded.if_in_octets == 1000
      assert decoded.if_out_octets == 2000
      assert decoded.timestamp == 1_640_000_000
    end
  end

  describe "HeartbeatMetadata" do
    test "creates struct with default values" do
      metadata = %HeartbeatMetadata{}
      assert metadata.version == ""
      assert metadata.hostname == ""
      assert metadata.uptime_seconds == 0
    end

    test "creates struct with values" do
      metadata = %HeartbeatMetadata{
        version: "1.0.0",
        hostname: "agent-1",
        uptime_seconds: 3600
      }

      assert metadata.version == "1.0.0"
      assert metadata.hostname == "agent-1"
      assert metadata.uptime_seconds == 3600
    end

    test "encodes and decodes" do
      metadata = %HeartbeatMetadata{
        version: "1.0.0",
        hostname: "agent-1",
        uptime_seconds: 3600
      }

      encoded = HeartbeatMetadata.encode(metadata)
      decoded = HeartbeatMetadata.decode(encoded)

      assert decoded.version == "1.0.0"
      assert decoded.hostname == "agent-1"
      assert decoded.uptime_seconds == 3600
    end
  end

  describe "HeartbeatResponse" do
    test "creates struct with default values" do
      response = %HeartbeatResponse{}
      assert response.status == ""
    end

    test "creates struct with values" do
      response = %HeartbeatResponse{status: "ok"}
      assert response.status == "ok"
    end

    test "encodes and decodes" do
      response = %HeartbeatResponse{status: "ok"}

      encoded = HeartbeatResponse.encode(response)
      decoded = HeartbeatResponse.decode(encoded)

      assert decoded.status == "ok"
    end
  end
end
