package demo;

import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class Main {

    // 1. Unidirectional @ManyToOne: the contact owns the relation
    @Entity(name = "Contact") @Table(name = "contacts")
    static class Contact1 {
        @Id @GeneratedValue Long id;
        String type;
        String data;
        @ManyToOne User1 user;
    }
    @Entity(name = "User") @Table(name = "users")
    static class User1 {
        @Id @GeneratedValue Long id;
        String username;
    }

    // 2. Unidirectional @OneToMany without @JoinColumn
    @Entity(name = "Contact") @Table(name = "contacts")
    static class Contact2 {
        @Id @GeneratedValue Long id;
        String type;
        String data;
    }
    @Entity(name = "User") @Table(name = "users")
    static class User2 {
        @Id @GeneratedValue Long id;
        String username;
        @OneToMany List<Contact2> contacts = new ArrayList<>();
    }

    // 3. Unidirectional @OneToMany with @JoinColumn
    @Entity(name = "Contact") @Table(name = "contacts")
    static class Contact3 {
        @Id @GeneratedValue Long id;
        String type;
        String data;
    }
    @Entity(name = "User") @Table(name = "users")
    static class User3 {
        @Id @GeneratedValue Long id;
        String username;
        @OneToMany @JoinColumn(name = "user_id") List<Contact3> contacts = new ArrayList<>();
    }

    // 4. @ManyToMany on both sides, no mappedBy: two unidirectional relations
    @Entity(name = "User") @Table(name = "users")
    static class User4 {
        @Id @GeneratedValue Long id;
        String username;
        @ManyToMany List<Role4> roles = new ArrayList<>();
    }
    @Entity(name = "Role") @Table(name = "roles")
    static class Role4 {
        @Id @GeneratedValue Long id;
        String name;
        @ManyToMany List<User4> users = new ArrayList<>();
    }

    // 5. Bidirectional @ManyToMany with mappedBy
    @Entity(name = "User") @Table(name = "users")
    static class User5 {
        @Id @GeneratedValue Long id;
        String username;
        @ManyToMany List<Role5> roles = new ArrayList<>();
    }
    @Entity(name = "Role") @Table(name = "roles")
    static class Role5 {
        @Id @GeneratedValue Long id;
        String name;
        @ManyToMany(mappedBy = "roles") List<User5> users = new ArrayList<>();
    }

    // 6. Bidirectional @OneToMany / @ManyToOne with mappedBy and helper methods
    @Entity(name = "Contact") @Table(name = "contacts")
    static class Contact6 {
        @Id @GeneratedValue Long id;
        String type;
        String data;
        @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") User6 user;
    }
    @Entity(name = "User") @Table(name = "users")
    static class User6 {
        @Id @GeneratedValue Long id;
        String username;
        @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
        List<Contact6> contacts = new ArrayList<>();

        void addContact(Contact6 c) { contacts.add(c); c.user = this; }
        void removeContact(Contact6 c) { contacts.remove(c); c.user = null; }
    }

    static SessionFactory build(String title, Class<?>... classes) {
        System.out.println("\n===== " + title);
        Configuration cfg = new Configuration()
                .setProperty("hibernate.connection.url", "jdbc:h2:mem:" + title.hashCode())
                .setProperty("hibernate.hbm2ddl.auto", "create")
                .setProperty("hibernate.show_sql", "true")
                .setProperty("hibernate.format_sql", "true")
                .setProperty("hibernate.highlight_sql", "false");
        for (Class<?> c : classes) cfg.addAnnotatedClass(c);
        return cfg.buildSessionFactory();
    }

    public static void main(String[] args) {
        build("1 ManyToOne", Contact1.class, User1.class).close();
        build("2 OneToMany", Contact2.class, User2.class).close();
        try (SessionFactory sf = build("3 OneToMany+JoinColumn", Contact3.class, User3.class)) {
            System.out.println("\n----- 3: save a user with two contacts");
            sf.inTransaction(s -> {
                User3 u = new User3();
                u.username = "alex";
                Contact3 a = new Contact3(); a.type = "email"; a.data = "a@example.com";
                Contact3 b = new Contact3(); b.type = "phone"; b.data = "+420 000";
                u.contacts.add(a);
                u.contacts.add(b);
                s.persist(a);
                s.persist(b);
                s.persist(u);
            });
            System.out.println("----- 3: end");
        }
        build("4 ManyToMany both sides", User4.class, Role4.class).close();
        build("5 ManyToMany mappedBy", User5.class, Role5.class).close();
        try (SessionFactory sf = build("6 bidirectional", Contact6.class, User6.class)) {
            System.out.println("\n----- 6: save a user with two contacts");
            sf.inTransaction(s -> {
                User6 u = new User6();
                u.username = "alex";
                Contact6 a = new Contact6(); a.type = "email"; a.data = "a@example.com";
                Contact6 b = new Contact6(); b.type = "phone"; b.data = "+420 000";
                u.addContact(a);
                u.addContact(b);
                s.persist(u);
            });
            System.out.println("\n----- 6: the inverse side alone does not write the foreign key");
            sf.inTransaction(s -> {
                User6 u = s.createQuery("from User", User6.class).getSingleResult();
                Contact6 c = new Contact6(); c.type = "telegram"; c.data = "@alex";
                u.contacts.add(c);          // only the inverse side is set
                s.persist(c);
            });
            sf.inTransaction(s -> s.createNativeQuery("select type, user_id from contacts order by id", Object[].class)
                    .getResultList()
                    .forEach(r -> System.out.println("  " + r[0] + " -> user_id " + r[1])));
        }
    }
}
