package study.graphql;

import java.util.List;

import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.BatchMapping;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;

/**
 * U50 教学控制器：schema 类型到方法的绑定、参数校验、租户过滤与 N+1 的两种写法。
 * 数据访问全部走 LibraryRepository（H2 真实 SQL），并统计 SQL 次数用于 N+1 观察。
 */
@Controller
public class LibraryController {

    private final LibraryRepository repository;
    private final SqlCounter counter;

    public LibraryController(LibraryRepository repository, SqlCounter counter) {
        this.repository = repository;
        this.counter = counter;
    }

    @QueryMapping
    public List<Book> books(@Argument Integer first) {
        int limit = first == null ? 10 : first;
        // 租户来自 GraphQLContext（由拦截器从 HTTP 头写入），不是方法参数里可伪造的字段
        String tenant = TenantContext.currentTenant();
        return repository.findBooks(tenant, limit);
    }

    @QueryMapping
    public Book book(@Argument String id) {
        String tenant = TenantContext.currentTenant();
        return repository.findBook(tenant, id);
    }

    @QueryMapping
    public List<Author> authors() {
        return repository.findAllAuthors();
    }

    @MutationMapping
    @org.springframework.security.access.prepost.PreAuthorize("hasRole('ADMIN')")
    public Book addBook(@Argument @jakarta.validation.Valid BookInput input) {
        String tenant = TenantContext.currentTenant();
        long id = repository.insertBook(tenant, input.title(), Long.parseLong(input.authorId()));
        return repository.findBook(tenant, Long.toString(id));
    }

    /**
     * N+1 写法：每个 Book 的 author 单独查一次（每个父对象调用一次）。
     * 统计上：N 本书 → 1 + N 次 SQL。
     */
    @SchemaMapping(typeName = "Book", field = "author")
    public Author author(Book book) {
        return repository.findAuthorById(book.authorId());
    }

    /**
     * 批量写法：@BatchMapping 收到本次请求所有 Book，一次 SQL 取回全部作者。
     * 返回 Map<Book, Author>；spring-graphql 内部注册为 DataLoader，按父对象去重批处理。
     */
    @BatchMapping(typeName = "Book", field = "authorBatch")
    public java.util.Map<Book, Author> authorBatch(List<Book> books) {
        java.util.Set<Long> ids = books.stream().map(Book::authorId).collect(java.util.stream.Collectors.toSet());
        java.util.Map<Long, Author> byId = repository.findAuthorsByIds(ids);
        java.util.Map<Book, Author> result = new java.util.LinkedHashMap<>();
        for (Book book : books) {
            Author a = byId.get(book.authorId());
            if (a != null) {
                result.put(book, a);
            }
        }
        return result;
    }

    /**
     * DataLoader 走 BatchLoaderRegistry 的显式版本：与 @BatchMapping 等价但更底层，
     * 便于观察 key 去重与单次批调用。装载函数注册见 {@link BatchLoaderConfig}。
     */
    @SchemaMapping(typeName = "Book", field = "authorViaLoader")
    public java.util.concurrent.CompletableFuture<Author> authorViaLoader(
            Book book, org.dataloader.DataLoader<Long, Author> dataLoader) {
        return dataLoader.load(book.authorId());
    }

    public record BookInput(@jakarta.validation.constraints.NotBlank @jakarta.validation.constraints.Size(max = 40) String title,
                            @jakarta.validation.constraints.NotBlank String authorId) {
    }
}
