package com.avaje.tests.compositekeys;

import static org.junit.Assert.assertEquals;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.junit.Before;
import org.junit.Test;

import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;

/**
 * @see https://groups.google.com/forum/#!topic/ebean/VD1MV2-LrOc
 */
public class TestOnCascadeDeleteChildrenWithCompositeKeys extends BaseTestCase {



  @Before public void before() {

    if (isMsSqlServer()) return;

    // remove all the User records first
    Ebean.deleteAll(Ebean.find(User.class).findList());

    // insert 2 User records
    Ebean.save(new User(1L));
    Ebean.save(new User(2L));
  }

  /**
   * work fine when delete User one by one
   */
  @Test public void testDeleteById() {

    if (isMsSqlServer()) return;

    assertEquals(2, Ebean.find(User.class).findList().size());
    Ebean.delete(User.class, 1L);
    Ebean.delete(User.class, 2L);
    assertEquals(0, Ebean.find(User.class).findList().size());
  }

  /**
   * Wrong SQL is generated when deleting user by list of ID
   * SQL generated: delete from user_role where (user_id) in ((?,?),(?,?))
   */
  @Test public void testDeleteByIdList() {

    if (isMsSqlServer()) return;

    assertEquals(2, Ebean.find(User.class).findList().size());
    List<Long> ids = new ArrayList<Long>();
    ids.add(1L);
    ids.add(2L);

    Ebean.deleteAll(User.class, ids); // PersistenceException would be thrown here
    assertEquals(0, Ebean.find(User.class).findList().size());
  }
  
  @Test
  public void testFindByParentIdList() {

    if (isMsSqlServer()) return;

    assertEquals(2, Ebean.find(User.class).findList().size());
    
    SpiEbeanServer spiServer = (SpiEbeanServer)Ebean.getServer(null);
    
    BeanDescriptor<TestOnCascadeDeleteChildrenWithCompositeKeys.User> beanDescriptor = 
        spiServer.getBeanDescriptor(TestOnCascadeDeleteChildrenWithCompositeKeys.User.class);
    
    BeanPropertyAssocMany<?> beanProperty = (BeanPropertyAssocMany<?>)beanDescriptor.getBeanProperty("userRoles");

    List<Object> ids = new ArrayList<Object>();
    ids.add(1L);
    ids.add(2L);

    beanProperty.findIdsByParentId(null, ids, null, null);
    beanProperty.findIdsByParentId(1L, null, null, null);
  }

  @Entity @Table(name = "em_user") public static class User {
    private Long id;
    private String name;
    private Set<UserRole> userRoles;

    public User() {}

    public User(Long id) {
      this.id = id;
    }

    @Id public Long getId() {
      return id;
    }

    public void setId(Long id) {
      this.id = id;
    }

    public String getName() {
      return name;
    }

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

    @OneToMany(cascade = CascadeType.REMOVE) public Set<UserRole> getUserRoles() {
      return userRoles;
    }

    public void setUserRoles(Set<UserRole> userRoles) {
      this.userRoles = userRoles;
    }
  }

  @Entity @Table(name = "em_user_role") public static class UserRole implements Serializable {
    private static final long serialVersionUID = 1L;
    private UserRolePK pk;
    private User user;
    private Role role;

    @EmbeddedId public UserRolePK getPk() {
      return pk;
    }

    public void setPk(UserRolePK pk) {
      this.pk = pk;
    }

    @ManyToOne @JoinColumn(name = "user_id", nullable = false, insertable = false, updatable = false) public User getUser() {
      return user;
    }

    public void setUser(User user) {
      this.user = user;
    }

    @ManyToOne @JoinColumn(name = "role_id", nullable = false, insertable = false, updatable = false) public Role getRole() {
      return role;
    }

    public void setRole(Role role) {
      this.role = role;
    }

    @Embeddable public static class UserRolePK implements Serializable {
      private static final long serialVersionUID = 1L;
      private Long userId;
      private Long roleId;

      @Override public int hashCode() {
        return userId == null || roleId == null ? 0 : (int) (userId + roleId);
      }

      @Override public boolean equals(Object other) {
        if (userId == null || roleId == null) return false;
        if (other instanceof UserRolePK) {
          UserRolePK otherPk = (UserRolePK) other;
          return userId.equals(otherPk.userId) && roleId.equals(otherPk.roleId);
        }
        return false;
      }

      public Long getUserId() {
        return userId;
      }

      public void setUserId(Long userId) {
        this.userId = userId;
      }

      public Long getRoleId() {
        return roleId;
      }

      public void setRoleId(Long roleId) {
        this.roleId = roleId;
      }
    }
  }

  @Entity @Table(name = "em_role") public static class Role {
    private Long id;
    private String name;
    private Set<UserRole> userRoles;

    @Id public Long getId() {
      return id;
    }

    public void setId(Long id) {
      this.id = id;
    }

    public String getName() {
      return name;
    }

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

    @OneToMany(cascade = CascadeType.REMOVE) public Set<UserRole> getUserRoles() {
      return userRoles;
    }

    public void setUserRoles(Set<UserRole> userRoles) {
      this.userRoles = userRoles;
    }
  }
}