package com.cameleer.server.app.storage;

import com.cameleer.server.core.rbac.*;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.*;

@Repository
public class PostgresGroupRepository implements GroupRepository {

    private final JdbcTemplate jdbc;

    public PostgresGroupRepository(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }

    @Override
    public List<GroupSummary> findAll() {
        return jdbc.query("SELECT id, name FROM groups ORDER BY name",
                (rs, rowNum) -> new GroupSummary(rs.getObject("id", UUID.class), rs.getString("name")));
    }

    @Override
    public Optional<GroupDetail> findById(UUID id) {
        var rows = jdbc.query(
                "SELECT id, name, parent_group_id, created_at FROM groups WHERE id = ?",
                (rs, rowNum) -> new GroupDetail(
                        rs.getObject("id", UUID.class),
                        rs.getString("name"),
                        rs.getObject("parent_group_id", UUID.class),
                        rs.getTimestamp("created_at").toInstant(),
                        List.of(), List.of(), List.of(), List.of()
                ), id);
        if (rows.isEmpty()) return Optional.empty();
        var g = rows.get(0);

        List<RoleSummary> directRoles = jdbc.query("""
                SELECT r.id, r.name, r.system FROM group_roles gr
                JOIN roles r ON r.id = gr.role_id WHERE gr.group_id = ?
                """, (rs, rowNum) -> new RoleSummary(rs.getObject("id", UUID.class),
                        rs.getString("name"), rs.getBoolean("system"), "direct"), id);

        List<UserSummary> members = jdbc.query("""
                SELECT u.user_id, u.display_name, u.provider FROM user_groups ug
                JOIN users u ON u.user_id = ug.user_id WHERE ug.group_id = ?
                """, (rs, rowNum) -> new UserSummary(rs.getString("user_id"),
                        rs.getString("display_name"), rs.getString("provider")), id);

        List<GroupSummary> children = findChildGroups(id);

        return Optional.of(new GroupDetail(g.id(), g.name(), g.parentGroupId(),
                g.createdAt(), directRoles, List.of(), members, children));
    }

    @Override
    public UUID create(String name, UUID parentGroupId) {
        UUID id = UUID.randomUUID();
        jdbc.update("INSERT INTO groups (id, name, parent_group_id) VALUES (?, ?, ?)",
                id, name, parentGroupId);
        return id;
    }

    @Override
    public void update(UUID id, String name, UUID parentGroupId) {
        jdbc.update("UPDATE groups SET name = COALESCE(?, name), parent_group_id = ? WHERE id = ?",
                name, parentGroupId, id);
    }

    @Override
    public void delete(UUID id) {
        jdbc.update("DELETE FROM groups WHERE id = ?", id);
    }

    @Override
    public void addRole(UUID groupId, UUID roleId) {
        jdbc.update("INSERT INTO group_roles (group_id, role_id) VALUES (?, ?) ON CONFLICT DO NOTHING",
                groupId, roleId);
    }

    @Override
    public void removeRole(UUID groupId, UUID roleId) {
        jdbc.update("DELETE FROM group_roles WHERE group_id = ? AND role_id = ?", groupId, roleId);
    }

    @Override
    public List<GroupSummary> findChildGroups(UUID parentId) {
        return jdbc.query("SELECT id, name FROM groups WHERE parent_group_id = ? ORDER BY name",
                (rs, rowNum) -> new GroupSummary(rs.getObject("id", UUID.class), rs.getString("name")),
                parentId);
    }

    @Override
    public List<GroupSummary> findAncestorChain(UUID groupId) {
        List<GroupSummary> chain = new ArrayList<>();
        UUID current = groupId;
        Set<UUID> visited = new HashSet<>();
        while (current != null && visited.add(current)) {
            UUID id = current;
            var rows = jdbc.query(
                    "SELECT id, name, parent_group_id FROM groups WHERE id = ?",
                    (rs, rowNum) -> new Object[]{
                            new GroupSummary(rs.getObject("id", UUID.class), rs.getString("name")),
                            rs.getObject("parent_group_id", UUID.class)
                    }, id);
            if (rows.isEmpty()) break;
            chain.add((GroupSummary) rows.get(0)[0]);
            current = (UUID) rows.get(0)[1];
        }
        Collections.reverse(chain);
        return chain;
    }
}
