package propertymapper;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Properties;

import static org.junit.jupiter.api.Assertions.*;


public class PropertyMapperTest {
  @Nested
  public class Q1 {

    public static final class PersonBean {
      private String name;
      private String email;

      public PersonBean() {
      }

      public void setName(String name) {
        this.name = name;
      }

      public void setEmail(String email) {
        this.email = email;
      }
    }

    @Test
    public void testExtractThrowsNullPointerExceptionForNullPrefix() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      assertThrows(NullPointerException.class,
          () -> mapper.extract(null, PersonBean.class));
    }

    @Test
    public void testExtractThrowsNullPointerExceptionForNullBeanType() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      assertThrows(NullPointerException.class,
          () -> mapper.extract("prefix", null));
    }

    public static final class AppBean {
      private String name;
      private String version;

      public AppBean() {
      }

      public void setName(String name) {
        this.name = name;
      }

      public void setVersion(String version) {
        this.version = version;
      }

      public String getName() {
        return name;
      }

      public String getVersion() {
        return version;
      }
    }

    @Test
    public void testExtractWithValidPrefix() {
      var propertyText = """
          app.name=TestApp
          app.version=1.0
          """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("app", AppBean.class);

      assertEquals("TestApp", bean.getName());
      assertEquals("1.0", bean.getVersion());
    }

    @Test
    public void testExtractReturnsCorrectBeanType() {
      var propertyText = """
          bean.name=John
          bean.email=john@foo.com
          """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("bean", PersonBean.class);

      assertInstanceOf(PersonBean.class, bean);
      assertEquals("John", bean.name);
      assertEquals("john@foo.com", bean.email);
    }

    @Test
    public void testExtractWithEmptyProperties() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var bean = mapper.extract("prefix", PersonBean.class);

      assertInstanceOf(PersonBean.class, bean);
      assertNull(bean.name);
      assertNull(bean.email);
    }

    @Test
    public void testExtractWithPrefixNotMatching() {
      var propertyText = """
          other.name=value
          """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("notfound", PersonBean.class);

      assertInstanceOf(PersonBean.class, bean);
      assertNull(bean.name);
      assertNull(bean.email);
    }

    @Test
    public void testExtractSetterDoNotChangeThePropertyValue() {
      var properties = new Properties();
      properties.setProperty("bob.name", "Bob");
      var mapper = PropertyMapper.create(properties);

      var bean = mapper.extract("bob", PersonBean.class);
      bean.setName("Tonio");

      assertEquals("Bob", properties.getProperty("bob.name"));
    }

    public static class FakeBean {
      private int counter;

      void setName(String name) {
        counter++;
      }
    }

    @Test
    public void testExtractDoNotCallTheSetterIfThePropertyIsNotPresent() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      // this should not call setName() given there is no property "name=..."
      var bean = mapper.extract("", FakeBean.class);

      assertEquals(0, bean.counter);
    }
  }


  @Nested
  public class Q2 {
    public static final class PersonBean {
      private String name;
      private String email;

      public PersonBean() {
      }

      public void setName(String name) {
        this.name = name;
      }

      public void setEmail(String email) {
        this.email = email;
      }
    }

    @Test
    public void testExtractEmptyPrefix() {
      var propertyText = """
          name=Alice
          email=alice@example.com
          """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("", PersonBean.class);

      assertInstanceOf(PersonBean.class, bean);
      assertEquals("Alice", bean.name);
      assertEquals("alice@example.com", bean.email);
    }

    @Test
    public void testExtractWithEmptyPropertiesAndEmptyPrefix() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var bean = mapper.extract("", PersonBean.class);

      assertInstanceOf(PersonBean.class, bean);
      assertNull(bean.name);
      assertNull(bean.email);
    }
  }

  @Nested
  public class Q3 {

    public interface Person {
      String getName();
      String getEmail();
    }

    @Test
    public void testProxyThrowsNullPointerExceptionForNullPrefix() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      assertThrows(NullPointerException.class,
          () -> mapper.proxy(null, Person.class));
    }

    @Test
    public void testProxyThrowsNullPointerExceptionForNullType() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      assertThrows(NullPointerException.class,
          () -> mapper.proxy("prefix", null));
    }

    @Test
    public void testProxyThrowsIllegalArgumentExceptionForNonInterface() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var exception = assertThrows(IllegalArgumentException.class,
          () -> mapper.proxy("prefix", String.class));
      assertFalse(exception.getMessage().isEmpty());
    }

    public interface App {
      String getName();
      String getVersion();
    }

    @Test
    public void testProxyCreatesValidProxy() {
      var propertyText = """
          app.name=TestApp
          app.version=1.0
          """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("app", App.class);

      assertInstanceOf(App.class, proxy);
      assertEquals("TestApp", proxy.getName());
      assertEquals("1.0", proxy.getVersion());
    }

    @Test
    public void testProxySeesPropertiesChanges() {
      var properties = new Properties();
      properties.setProperty("app.name", "TestApp");
      properties.setProperty("app.version", "1.0");
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("app", App.class);
      assertEquals("1.0", proxy.getVersion());

      properties.setProperty("app.version", "2.0");
      assertEquals("2.0", proxy.getVersion());
    }

    @Test
    public void testProxyGetterReturnsNullForMissingProperty() {
      var propertyText = """
          user.name=Ana
          """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("user", Person.class);

      assertInstanceOf(Person.class, proxy);
      assertEquals("Ana", proxy.getName());
      assertNull(proxy.getEmail());
    }

    @Test
    public void testProxyWithEmptyProperties() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("empty", Person.class);

      assertInstanceOf(Person.class, proxy);
      assertNull(proxy.getName());
      assertNull(proxy.getEmail());
    }

    @Test
    public void testProxyWithEmptyPrefixProperties() {
      var propertyText = """
          name=Section1
          email=section@test.com
          """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("", Person.class);

      assertInstanceOf(Person.class, proxy);
      assertEquals("Section1", proxy.getName());
      assertEquals("section@test.com", proxy.getEmail());
    }

    public interface Empty {}

    @Test
    public void testProxyEmptyInterface() {
      var propertyText = """
          foo=Foo
          bar=Bar
          """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("foo", Empty.class);
      assertInstanceOf(Empty.class, proxy);
    }

    public interface Named {
      String getName();
    }

    @Test
    public void testProxyNotAllProperties() {
      var propertyText = """
          user.name=Foo
          user.email=foo@bar.com
          """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("user", Named.class);
      assertInstanceOf(Named.class, proxy);
      assertEquals("Foo", proxy.getName());
    }

    @Test
    public void testProxyMultipleCallsWithSamePrefix() {
      var propertyText = """
          data.name=First
          """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy1 = mapper.proxy("data", Person.class);
      var proxy2 = mapper.proxy("data", Person.class);

      assertEquals(proxy1.getName(), proxy2.getName());
    }

    @Test
    public void testProxyMethodsNotSupported() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("person", Person.class);

      assertThrows(UnsupportedOperationException.class, () -> proxy.equals(null));
      assertThrows(UnsupportedOperationException.class, () -> proxy.toString());
      assertThrows(UnsupportedOperationException.class, () -> proxy.hashCode());
    }

  }


  @Nested
  public class Q4 {

    public interface Person {
      String getName();
      String getEmail();
      void setName(String name);
      void setEmail(String email);
    }

    @Test
    public void testProxySetterUpdatesProperty() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("user", Person.class);
      proxy.setName("Alice");
      proxy.setEmail("alice@example.com");

      assertEquals("Alice", proxy.getName());
      assertEquals("alice@example.com", proxy.getEmail());
    }

    @Test
    public void testProxySetterThenSetterAgain() {
      var propertyText = "test.name=Initial";
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("test", Person.class);
      proxy.setName("value1");
      proxy.setName("value2");

      assertEquals("value2", proxy.getName());
    }

    public interface App {
      String getName();
      String getVersion();
      void setVersion(String version);
    }

    @Test
    public void testProxyUpdatesProperties() {
      var properties = new Properties();
      properties.setProperty("app.name", "TestApp");
      properties.setProperty("app.version", "1.0");
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("app", App.class);
      proxy.setVersion("2.0");

      assertEquals("2.0", proxy.getVersion());
      assertEquals("2.0", properties.getProperty("app.version"));
    }
  }


  @Nested
  public class Q6 {
    public interface Address {
      String getStreet();
    }
    public interface Person {
      String getName();
      Address getAddress();
    }

    @Test
    public void testRecursiveDefinition() {
      var propertyText = """
          user.name=Alice
          user.address.street=32 canyon road
      """;
      var mapper = PropertyMapper.create(propertyText);
      var person = mapper.proxy("user",  Person.class);
      var address = person.getAddress();
      assertInstanceOf(Person.class, person);
      assertInstanceOf(Address.class, address);
      assertEquals("Alice", person.getName());
      assertEquals("32 canyon road", address.getStreet());
    }

    public interface Extra {
      String getInfo();
      void setInfo(String info);
    }
    public interface App {
      String getName();
      Extra getExtra();
    }

    @Test
    public void testRecursiveDefinitionWithUpdates() {
      var properties = new Properties();
      properties.setProperty("app.name", "MonkKay");
      properties.setProperty("app.extra.info", "5 stars");
      var mapper = PropertyMapper.create(properties);

      var app = mapper.proxy("app",  App.class);
      var extra = app.getExtra();
      assertInstanceOf(App.class, app);
      assertInstanceOf(Extra.class, extra);
      assertEquals("MonkKay", app.getName());
      assertEquals("5 stars", extra.getInfo());

      extra.setInfo("3 stars");
      assertEquals("3 stars", extra.getInfo());
      assertEquals("3 stars", properties.getProperty("app.extra.info")  );
    }
  }


  @Nested
  public class Q7 {

    public interface Person {
      String getName();
      String getEmail();
      void setName(String name);
      void setEmail(String email);
    }

    @Test
    public void testProxyFreezePreventsSetter() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("user", Person.class);
      proxy.setName("Alice");

      mapper.freeze();

      var exception = assertThrows(IllegalStateException.class,
          () -> proxy.setName("Bob"));
      assertFalse(exception.getMessage().isEmpty());
    }

    @Test
    public void testProxyFreezePreservesGetters() {
      var propertyText = """
        user.name=Alice
        user.email=alice@example.com
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("user", Person.class);
      mapper.freeze();

      assertEquals("Alice", proxy.getName());
      assertEquals("alice@example.com", proxy.getEmail());
    }

    @Test
    public void testProxyFreezeMultipleSetterAttempts() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("user", Person.class);
      proxy.setName("Alice");
      mapper.freeze();

      assertThrows(IllegalStateException.class,
          () -> proxy.setName("Bob"));
      assertThrows(IllegalStateException.class,
          () -> proxy.setEmail("bob@example.com"));
    }

    @Test
    public void testProxyFreezeErrorMessageIncludesPropertyName() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("app", Person.class);
      mapper.freeze();

      var exception = assertThrows(IllegalStateException.class,
          () -> proxy.setName("TestName"));
      assertFalse(exception.getMessage().isEmpty());
    }

    public interface App {
      String getName();
      String getVersion();
      void setVersion(String version);
    }

    @Test
    public void testProxyFreezeWithMultipleProxies() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var app = mapper.proxy("app", App.class);
      var user = mapper.proxy("user", Person.class);

      app.setVersion("1.0");
      user.setName("Alice");

      mapper.freeze();

      assertThrows(IllegalStateException.class,
          () -> app.setVersion("2.0"));
      assertThrows(IllegalStateException.class,
          () -> user.setEmail("alice@example.com"));
    }

    @Test
    public void testProxyFreezeIsIdempotent() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var app = mapper.proxy("app", App.class);
      mapper.freeze();
      mapper.freeze();

      assertThrows(IllegalStateException.class,
          () -> app.setVersion("3.0"));
    }

    @Test
    public void testProxyFreezeBeforeAnySetterCall() {
      var propertyText = """
        user.name=Alice
        user.email=alice@example.com
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("user", Person.class);
      mapper.freeze();

      var exception = assertThrows(IllegalStateException.class,
          () -> proxy.setName("Bob"));
      assertFalse(exception.getMessage().isEmpty());
    }

    @Test
    public void testProxySetterThenFreezePreventsFurtherChanges() {
      var propertyText = """
        user.name=Alice
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("user", Person.class);
      proxy.setName("Bob");
      assertEquals("Bob", proxy.getName());

      mapper.freeze();

      var exception = assertThrows(IllegalStateException.class,
          () -> proxy.setName("Charlie"));
      assertFalse(exception.getMessage().isEmpty());
    }
  }


  @Nested
  public class Q8 {

    // Tests for extract() with primitive types

    public static final class PersonBean {
      private int age;
      private double salary;
      private boolean active;
      private char grade;
      private long employeeId;
      private float rating;
      private byte level;
      private short department;

      public PersonBean() {
      }

      public void setAge(int age) {
        this.age = age;
      }

      public void setSalary(double salary) {
        this.salary = salary;
      }

      public void setActive(boolean active) {
        this.active = active;
      }

      public void setGrade(char grade) {
        this.grade = grade;
      }

      public void setEmployeeId(long employeeId) {
        this.employeeId = employeeId;
      }

      public void setRating(float rating) {
        this.rating = rating;
      }

      public void setLevel(byte level) {
        this.level = level;
      }

      public void setDepartment(short department) {
        this.department = department;
      }

      public int getAge() {
        return age;
      }

      public double getSalary() {
        return salary;
      }

      public boolean isActive() {
        return active;
      }

      public char getGrade() {
        return grade;
      }

      public long getEmployeeId() {
        return employeeId;
      }

      public float getRating() {
        return rating;
      }

      public byte getLevel() {
        return level;
      }

      public short getDepartment() {
        return department;
      }
    }

    @Test
    public void testExtractIntPrimitive() {
      var propertyText = """
        person.age=30
        """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("person", PersonBean.class);

      assertEquals(30, bean.getAge());
    }

    @Test
    public void testExtractDoublePrimitive() {
      var propertyText = """
        person.salary=50000.50
        """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("person", PersonBean.class);

      assertEquals(50000.50, bean.getSalary());
    }

    @Test
    public void testExtractBooleanPrimitive() {
      var propertyText = """
        person.active=true
        """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("person", PersonBean.class);

      assertTrue(bean.isActive());
    }

    @Test
    public void testExtractBooleanPrimitiveFalse() {
      var propertyText = """
        person.active=false
        """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("person", PersonBean.class);

      assertFalse(bean.isActive());
    }

    @Test
    public void testExtractCharPrimitive() {
      var propertyText = """
        person.grade=A
        """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("person", PersonBean.class);

      assertEquals('A', bean.getGrade());
    }

    @Test
    public void testExtractLongPrimitive() {
      var propertyText = """
        person.employeeId=9876543210
        """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("person", PersonBean.class);

      assertEquals(9876543210L, bean.getEmployeeId());
    }

    @Test
    public void testExtractFloatPrimitive() {
      var propertyText = """
        person.rating=4.5
        """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("person", PersonBean.class);

      assertEquals(4.5f, bean.getRating());
    }

    @Test
    public void testExtractBytePrimitive() {
      var propertyText = """
        person.level=5
        """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("person", PersonBean.class);

      assertEquals((byte) 5, bean.getLevel());
    }

    @Test
    public void testExtractShortPrimitive() {
      var propertyText = """
        person.department=100
        """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("person", PersonBean.class);

      assertEquals((short) 100, bean.getDepartment());
    }

    @Test
    public void testExtractMultiplePrimitiveTypes() {
      var propertyText = """
        emp.age=25
        emp.salary=45000.75
        emp.active=true
        emp.grade=B
        emp.employeeId=1234567890
        emp.rating=3.8
        emp.level=3
        emp.department=50
        """;
      var mapper = PropertyMapper.create(propertyText);

      var bean = mapper.extract("emp", PersonBean.class);

      assertEquals(25, bean.getAge());
      assertEquals(45000.75, bean.getSalary());
      assertTrue(bean.isActive());
      assertEquals('B', bean.getGrade());
      assertEquals(1234567890L, bean.getEmployeeId());
      assertEquals(3.8f, bean.getRating());
      assertEquals((byte) 3, bean.getLevel());
      assertEquals((short) 50, bean.getDepartment());
    }

    public static final class InvalidBean {
      private StringBuilder value;

      public InvalidBean() {}

      public void setValue(StringBuilder value) {
        this.value = value;
      }
    }

    @Test
    public void testInvalidBeanParameterType() {
      var properties = new Properties();
      properties.put("value", "text");
      var mapper = PropertyMapper.create(properties);

      var exception = assertThrows(IllegalStateException.class,
          () -> mapper.extract("", InvalidBean.class));
      assertFalse(exception.getMessage().isEmpty());
    }

    @Test
    public void testInvalidBeanParameterTypeNoProperty() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var bean = mapper.extract("", InvalidBean.class);
      assertNotNull(bean);
    }



    public interface Employee {
      int getAge();
      void setAge(int age);
      double getSalary();
      void setSalary(double salary);
      boolean isActive();
      void setActive(boolean active);
      char getGrade();
      void setGrade(char grade);
      long getEmployeeId();
      void setEmployeeId(long employeeId);
      float getRating();
      void setRating(float rating);
      byte getLevel();
      void setLevel(byte level);
      short getDepartment();
      void setDepartment(short department);
    }

    @Test
    public void testProxyGetterIntPrimitive() {
      var propertyText = """
        employee.age=30
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("employee", Employee.class);

      assertEquals(30, proxy.getAge());
    }

    @Test
    public void testProxyGetterDoublePrimitive() {
      var propertyText = """
        employee.salary=75000.25
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("employee", Employee.class);

      assertEquals(75000.25, proxy.getSalary());
    }

    @Test
    public void testProxyGetterBooleanPrimitive() {
      var propertyText = """
        employee.active=true
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("employee", Employee.class);

      assertTrue(proxy.isActive());
    }

    @Test
    public void testProxyGetterCharPrimitive() {
      var propertyText = """
        employee.grade=C
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("employee", Employee.class);

      assertEquals('C', proxy.getGrade());
    }

    @Test
    public void testProxyGetterLongPrimitive() {
      var propertyText = """
        employee.employeeId=5555555555
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("employee", Employee.class);

      assertEquals(5555555555L, proxy.getEmployeeId());
    }

    @Test
    public void testProxyGetterFloatPrimitive() {
      var propertyText = """
        employee.rating=2.5
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("employee", Employee.class);

      assertEquals(2.5f, proxy.getRating());
    }

    @Test
    public void testProxyGetterBytePrimitive() {
      var propertyText = """
        employee.level=7
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("employee", Employee.class);

      assertEquals((byte) 7, proxy.getLevel());
    }

    @Test
    public void testProxyGetterShortPrimitive() {
      var propertyText = """
        employee.department=200
        """;
      var mapper = PropertyMapper.create(propertyText);

      var proxy = mapper.proxy("employee", Employee.class);

      assertEquals((short) 200, proxy.getDepartment());
    }

    @Test
    public void testProxySetterIntPrimitive() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("employee", Employee.class);
      proxy.setAge(28);

      assertEquals(28, proxy.getAge());
      assertEquals("28", properties.getProperty("employee.age"));
    }

    @Test
    public void testProxySetterDoublePrimitive() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("employee", Employee.class);
      proxy.setSalary(60000.99);

      assertEquals(60000.99, proxy.getSalary());
      assertEquals("60000.99", properties.getProperty("employee.salary"));
    }

    @Test
    public void testProxySetterBooleanPrimitive() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("employee", Employee.class);
      proxy.setActive(false);

      assertFalse(proxy.isActive());
      assertEquals("false", properties.getProperty("employee.active"));
    }

    @Test
    public void testProxySetterCharPrimitive() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("employee", Employee.class);
      proxy.setGrade('D');

      assertEquals('D', proxy.getGrade());
      assertEquals("D", properties.getProperty("employee.grade"));
    }

    @Test
    public void testProxySetterLongPrimitive() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("employee", Employee.class);
      proxy.setEmployeeId(9999999999L);

      assertEquals(9999999999L, proxy.getEmployeeId());
      assertEquals("9999999999", properties.getProperty("employee.employeeId"));
    }

    @Test
    public void testProxySetterFloatPrimitive() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("employee", Employee.class);
      proxy.setRating(4.2f);

      assertEquals(4.2f, proxy.getRating());
      assertEquals("4.2", properties.getProperty("employee.rating"));
    }

    @Test
    public void testProxySetterBytePrimitive() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("employee", Employee.class);
      proxy.setLevel((byte) 9);

      assertEquals((byte) 9, proxy.getLevel());
      assertEquals("9", properties.getProperty("employee.level"));
    }

    @Test
    public void testProxySetterShortPrimitive() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("employee", Employee.class);
      proxy.setDepartment((short) 150);

      assertEquals((short) 150, proxy.getDepartment());
      assertEquals("150", properties.getProperty("employee.department"));
    }

    @Test
    public void testProxyMultiplePrimitiveTypeGettersAndSetters() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("employee", Employee.class);
      proxy.setAge(35);
      proxy.setSalary(85000.50);
      proxy.setActive(true);
      proxy.setGrade('A');
      proxy.setEmployeeId(4444444444L);
      proxy.setRating(4.9f);
      proxy.setLevel((byte) 10);
      proxy.setDepartment((short) 300);

      assertEquals(35, proxy.getAge());
      assertEquals(85000.50, proxy.getSalary());
      assertTrue(proxy.isActive());
      assertEquals('A', proxy.getGrade());
      assertEquals(4444444444L, proxy.getEmployeeId());
      assertEquals(4.9f, proxy.getRating());
      assertEquals((byte) 10, proxy.getLevel());
      assertEquals((short) 300, proxy.getDepartment());
    }

    @Test
    public void testProxyPrimitivesSeesPropertyChanges() {
      var properties = new Properties();
      properties.setProperty("employee.age", "25");
      properties.setProperty("employee.salary", "50000.0");
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("employee", Employee.class);
      assertEquals(25, proxy.getAge());
      assertEquals(50000.0, proxy.getSalary());

      properties.setProperty("employee.age", "26");
      properties.setProperty("employee.salary", "51000.0");

      assertEquals(26, proxy.getAge());
      assertEquals(51000.0, proxy.getSalary());
    }

    public interface InvalidType {
      void setData(StringBuilder builder);
      StringBuilder getData();
    }

    @Test
    public void testInvalidProxyParameterTypeNoProperty() {
      var properties = new Properties();
      var mapper = PropertyMapper.create(properties);

      var proxy = mapper.proxy("", InvalidType.class);
      var exception = assertThrows(IllegalStateException.class, proxy::getData);
      assertFalse(exception.getMessage().isEmpty());
    }
  }
}