package sql_test

import (
	"context"
	"testing"
	"tercul/internal/data/sql"
	"tercul/internal/domain"
	"tercul/internal/platform/config"
	"tercul/internal/testutil"

	"github.com/stretchr/testify/suite"
)

type BookRepositoryTestSuite struct {
	testutil.IntegrationTestSuite
	BookRepo domain.BookRepository
}

func (s *BookRepositoryTestSuite) SetupSuite() {
	s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
	cfg, err := config.LoadConfig()
	s.Require().NoError(err)
	s.BookRepo = sql.NewBookRepository(s.DB, cfg)
}

func (s *BookRepositoryTestSuite) SetupTest() {
	s.IntegrationTestSuite.SetupTest()
	s.DB.Exec("DELETE FROM books")
	s.DB.Exec("DELETE FROM authors")
	s.DB.Exec("DELETE FROM publishers")
	s.DB.Exec("DELETE FROM book_authors")
	s.DB.Exec("DELETE FROM book_works")
	s.DB.Exec("DELETE FROM works")
}

func (s *BookRepositoryTestSuite) createBook(title, isbn string) *domain.Book {
	book := &domain.Book{
		Title: title,
		ISBN:  isbn,
		TranslatableModel: domain.TranslatableModel{
			Language: "en",
		},
	}
	err := s.BookRepo.Create(context.Background(), book)
	s.Require().NoError(err)
	return book
}

func (s *BookRepositoryTestSuite) createAuthor(name string) *domain.Author {
	author := &domain.Author{Name: name}
	err := s.DB.Create(author).Error
	s.Require().NoError(err)
	return author
}

func (s *BookRepositoryTestSuite) createPublisher(name string) *domain.Publisher {
	publisher := &domain.Publisher{Name: name}
	err := s.DB.Create(publisher).Error
	s.Require().NoError(err)
	return publisher
}

func (s *BookRepositoryTestSuite) TestFindByISBN() {
	s.Run("should return a book by ISBN", func() {
		// Arrange
		s.createBook("Test Book", "1234567890")

		// Act
		foundBook, err := s.BookRepo.FindByISBN(context.Background(), "1234567890")

		// Assert
		s.Require().NoError(err)
		s.Require().NotNil(foundBook)
		s.Equal("Test Book", foundBook.Title)
	})

	s.Run("should return error if ISBN not found", func() {
		// Arrange
		s.createBook("Another Book", "1111111111")

		// Act
		_, err := s.BookRepo.FindByISBN(context.Background(), "9999999999")

		// Assert
		s.Require().Error(err)
	})
}

func TestBookRepository(t *testing.T) {
	suite.Run(t, new(BookRepositoryTestSuite))
}

func (s *BookRepositoryTestSuite) TestListByAuthorID() {
	s.Run("should return all books for a given author", func() {
		// Arrange
		author1 := s.createAuthor("Test Author 1")
		author2 := s.createAuthor("Test Author 2")
		book1 := s.createBook("Book 1 by Author 1", "111")
		book2 := s.createBook("Book 2 by Author 1", "222")
		book3 := s.createBook("Book 3 by Author 2", "333")

		s.Require().NoError(s.DB.Model(&author1).Association("Books").Append([]*domain.Book{book1, book2}))
		s.Require().NoError(s.DB.Model(&author2).Association("Books").Append(book3))

		// Act
		books, err := s.BookRepo.ListByAuthorID(context.Background(), author1.ID)

		// Assert
		s.Require().NoError(err)
		s.Len(books, 2)
		s.ElementsMatch([]string{"Book 1 by Author 1", "Book 2 by Author 1"}, []string{books[0].Title, books[1].Title})
	})
}

func (s *BookRepositoryTestSuite) TestListByPublisherID() {
	s.Run("should return all books for a given publisher", func() {
		// Arrange
		publisher1 := s.createPublisher("Publisher 1")
		publisher2 := s.createPublisher("Publisher 2")
		book1 := s.createBook("Book 1 from Publisher 1", "111")
		book2 := s.createBook("Book 2 from Publisher 1", "222")
		book3 := s.createBook("Book 3 from Publisher 2", "333")

		book1.PublisherID = &publisher1.ID
		book2.PublisherID = &publisher1.ID
		book3.PublisherID = &publisher2.ID
		s.Require().NoError(s.DB.Save(book1).Error)
		s.Require().NoError(s.DB.Save(book2).Error)
		s.Require().NoError(s.DB.Save(book3).Error)

		// Act
		books, err := s.BookRepo.ListByPublisherID(context.Background(), publisher1.ID)

		// Assert
		s.Require().NoError(err)
		s.Len(books, 2)
		s.ElementsMatch([]string{"Book 1 from Publisher 1", "Book 2 from Publisher 1"}, []string{books[0].Title, books[1].Title})
	})
}

func (s *BookRepositoryTestSuite) TestListByWorkID() {
	s.Run("should return all books associated with a given work", func() {
		// Arrange
		work1 := s.CreateTestWork(s.AdminCtx, "Work 1", "en", "content 1")
		work2 := s.CreateTestWork(s.AdminCtx, "Work 2", "en", "content 2")
		book1 := s.createBook("Book 1 for Work 1", "111")
		book2 := s.createBook("Book 2 for Work 1", "222")
		book3 := s.createBook("Book 3 for Work 2", "333")

		// Manually create the association in the join table
		s.Require().NoError(s.DB.Exec("INSERT INTO book_works (book_id, work_id) VALUES (?, ?)", book1.ID, work1.ID).Error)
		s.Require().NoError(s.DB.Exec("INSERT INTO book_works (book_id, work_id) VALUES (?, ?)", book2.ID, work1.ID).Error)
		s.Require().NoError(s.DB.Exec("INSERT INTO book_works (book_id, work_id) VALUES (?, ?)", book3.ID, work2.ID).Error)

		// Act
		books, err := s.BookRepo.ListByWorkID(context.Background(), work1.ID)

		// Assert
		s.Require().NoError(err)
		s.Len(books, 2)
		s.ElementsMatch([]string{"Book 1 for Work 1", "Book 2 for Work 1"}, []string{books[0].Title, books[1].Title})
	})
}