package sql_test

import (
	"context"
	"database/sql/driver"
	"testing"
	"tercul/internal/data/sql"
	"tercul/internal/domain"
	"tercul/internal/platform/config"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/suite"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

// AnyTime is used to match any time.Time value in sqlmock.
type AnyTime struct{}

// Match satisfies sqlmock.Argument interface
func (a AnyTime) Match(v driver.Value) bool {
	_, ok := v.(time.Time)
	return ok
}

// CopyrightRepositoryTestSuite is the test suite for CopyrightRepository.
type CopyrightRepositoryTestSuite struct {
	suite.Suite
	db   *gorm.DB
	mock sqlmock.Sqlmock
	repo domain.CopyrightRepository
}

// SetupTest sets up the test environment.
func (s *CopyrightRepositoryTestSuite) SetupTest() {
	db, mock, err := sqlmock.New()
	s.Require().NoError(err)

	gormDB, err := gorm.Open(postgres.New(postgres.Config{Conn: db}), &gorm.Config{})
	s.Require().NoError(err)

	s.db = gormDB
	s.mock = mock
	cfg, err := config.LoadConfig()
	s.Require().NoError(err)
	s.repo = sql.NewCopyrightRepository(s.db, cfg)
}

// TearDownTest checks if all expectations were met.
func (s *CopyrightRepositoryTestSuite) TearDownTest() {
	s.Require().NoError(s.mock.ExpectationsWereMet())
}

// TestCopyrightRepositoryTestSuite runs the test suite.
func TestCopyrightRepositoryTestSuite(t *testing.T) {
	suite.Run(t, new(CopyrightRepositoryTestSuite))
}

func (s *CopyrightRepositoryTestSuite) TestNewCopyrightRepository() {
	s.Run("should create a new repository", func() {
		s.NotNil(s.repo)
	})
}

func (s *CopyrightRepositoryTestSuite) TestAddTranslation() {
	s.Run("should add a translation", func() {
		translation := &domain.CopyrightTranslation{
			CopyrightID:  1,
			LanguageCode: "en",
			Message:      "Test message",
			Description:  "",
		}

		s.mock.ExpectBegin()
		s.mock.ExpectQuery(`INSERT INTO "copyright_translations" \("created_at","updated_at","copyright_id","language_code","message","description"\) VALUES \(\$1,\$2,\$3,\$4,\$5,\$6\) RETURNING "id"`).
			WithArgs(AnyTime{}, AnyTime{}, translation.CopyrightID, translation.LanguageCode, translation.Message, translation.Description).
			WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
		s.mock.ExpectCommit()

		err := s.repo.AddTranslation(context.Background(), translation)
		s.Require().NoError(err)
	})
}

func (s *CopyrightRepositoryTestSuite) TestGetTranslations() {
	s.Run("should get all translations for a copyright", func() {
		copyrightID := uint(1)
		rows := sqlmock.NewRows([]string{"id", "copyright_id", "language_code", "message"}).
			AddRow(1, copyrightID, "en", "English message").
			AddRow(2, copyrightID, "es", "Spanish message")

		s.mock.ExpectQuery(`SELECT \* FROM "copyright_translations" WHERE copyright_id = \$1`).
			WithArgs(copyrightID).
			WillReturnRows(rows)

		translations, err := s.repo.GetTranslations(context.Background(), copyrightID)
		s.Require().NoError(err)
		s.Require().Len(translations, 2)
	})
}

func (s *CopyrightRepositoryTestSuite) TestGetTranslationByLanguage() {
	s.Run("should get a specific translation by language code", func() {
		copyrightID := uint(1)
		languageCode := "en"
		rows := sqlmock.NewRows([]string{"id", "copyright_id", "language_code", "message"}).
			AddRow(1, copyrightID, languageCode, "English message")

		s.mock.ExpectQuery(`SELECT \* FROM "copyright_translations" WHERE copyright_id = \$1 AND language_code = \$2 ORDER BY "copyright_translations"\."id" LIMIT \$3`).
			WithArgs(copyrightID, languageCode, 1).
			WillReturnRows(rows)

		translation, err := s.repo.GetTranslationByLanguage(context.Background(), copyrightID, languageCode)
		s.Require().NoError(err)
		s.Require().NotNil(translation)
		s.Require().Equal(languageCode, translation.LanguageCode)
	})

	s.Run("should return ErrEntityNotFound for non-existent translation", func() {
		copyrightID := uint(1)
		languageCode := "en"

		s.mock.ExpectQuery(`SELECT \* FROM "copyright_translations" WHERE copyright_id = \$1 AND language_code = \$2 ORDER BY "copyright_translations"\."id" LIMIT \$3`).
			WithArgs(copyrightID, languageCode, 1).
			WillReturnError(gorm.ErrRecordNotFound)

		_, err := s.repo.GetTranslationByLanguage(context.Background(), copyrightID, languageCode)
		s.Require().Error(err)
		s.Require().Contains(err.Error(), "entity not found")
	})
}

func (s *CopyrightRepositoryTestSuite) TestAddCopyrightToWork() {
	s.Run("should add a copyright to a work", func() {
		workID, copyrightID := uint(1), uint(1)
		s.mock.ExpectExec(`INSERT INTO work_copyrights \(work_id, copyright_id\) VALUES \(\$1, \$2\) ON CONFLICT DO NOTHING`).
			WithArgs(workID, copyrightID).
			WillReturnResult(sqlmock.NewResult(1, 1))
		err := s.repo.AddCopyrightToWork(context.Background(), workID, copyrightID)
		s.Require().NoError(err)
	})
}

func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromWork() {
	s.Run("should remove a copyright from a work", func() {
		workID, copyrightID := uint(1), uint(1)
		s.mock.ExpectExec(`DELETE FROM work_copyrights WHERE work_id = \$1 AND copyright_id = \$2`).
			WithArgs(workID, copyrightID).
			WillReturnResult(sqlmock.NewResult(1, 1))
		err := s.repo.RemoveCopyrightFromWork(context.Background(), workID, copyrightID)
		s.Require().NoError(err)
	})
}

func (s *CopyrightRepositoryTestSuite) TestAddCopyrightToAuthor() {
	s.Run("should add a copyright to an author", func() {
		authorID, copyrightID := uint(1), uint(1)
		s.mock.ExpectExec(`INSERT INTO author_copyrights \(author_id, copyright_id\) VALUES \(\$1, \$2\) ON CONFLICT DO NOTHING`).
			WithArgs(authorID, copyrightID).
			WillReturnResult(sqlmock.NewResult(1, 1))
		err := s.repo.AddCopyrightToAuthor(context.Background(), authorID, copyrightID)
		s.Require().NoError(err)
	})
}

func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromAuthor() {
	s.Run("should remove a copyright from an author", func() {
		authorID, copyrightID := uint(1), uint(1)
		s.mock.ExpectExec(`DELETE FROM author_copyrights WHERE author_id = \$1 AND copyright_id = \$2`).
			WithArgs(authorID, copyrightID).
			WillReturnResult(sqlmock.NewResult(1, 1))
		err := s.repo.RemoveCopyrightFromAuthor(context.Background(), authorID, copyrightID)
		s.Require().NoError(err)
	})
}

func (s *CopyrightRepositoryTestSuite) TestAddCopyrightToBook() {
	s.Run("should add a copyright to a book", func() {
		bookID, copyrightID := uint(1), uint(1)
		s.mock.ExpectExec(`INSERT INTO book_copyrights \(book_id, copyright_id\) VALUES \(\$1, \$2\) ON CONFLICT DO NOTHING`).
			WithArgs(bookID, copyrightID).
			WillReturnResult(sqlmock.NewResult(1, 1))
		err := s.repo.AddCopyrightToBook(context.Background(), bookID, copyrightID)
		s.Require().NoError(err)
	})
}

func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromBook() {
	s.Run("should remove a copyright from a book", func() {
		bookID, copyrightID := uint(1), uint(1)
		s.mock.ExpectExec(`DELETE FROM book_copyrights WHERE book_id = \$1 AND copyright_id = \$2`).
			WithArgs(bookID, copyrightID).
			WillReturnResult(sqlmock.NewResult(1, 1))
		err := s.repo.RemoveCopyrightFromBook(context.Background(), bookID, copyrightID)
		s.Require().NoError(err)
	})
}

func (s *CopyrightRepositoryTestSuite) TestAddCopyrightToPublisher() {
	s.Run("should add a copyright to a publisher", func() {
		publisherID, copyrightID := uint(1), uint(1)
		s.mock.ExpectExec(`INSERT INTO publisher_copyrights \(publisher_id, copyright_id\) VALUES \(\$1, \$2\) ON CONFLICT DO NOTHING`).
			WithArgs(publisherID, copyrightID).
			WillReturnResult(sqlmock.NewResult(1, 1))
		err := s.repo.AddCopyrightToPublisher(context.Background(), publisherID, copyrightID)
		s.Require().NoError(err)
	})
}

func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromPublisher() {
	s.Run("should remove a copyright from a publisher", func() {
		publisherID, copyrightID := uint(1), uint(1)
		s.mock.ExpectExec(`DELETE FROM publisher_copyrights WHERE publisher_id = \$1 AND copyright_id = \$2`).
			WithArgs(publisherID, copyrightID).
			WillReturnResult(sqlmock.NewResult(1, 1))
		err := s.repo.RemoveCopyrightFromPublisher(context.Background(), publisherID, copyrightID)
		s.Require().NoError(err)
	})
}

func (s *CopyrightRepositoryTestSuite) TestAddCopyrightToSource() {
	s.Run("should add a copyright to a source", func() {
		sourceID, copyrightID := uint(1), uint(1)
		s.mock.ExpectExec(`INSERT INTO source_copyrights \(source_id, copyright_id\) VALUES \(\$1, \$2\) ON CONFLICT DO NOTHING`).
			WithArgs(sourceID, copyrightID).
			WillReturnResult(sqlmock.NewResult(1, 1))
		err := s.repo.AddCopyrightToSource(context.Background(), sourceID, copyrightID)
		s.Require().NoError(err)
	})
}

func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromSource() {
	s.Run("should remove a copyright from a source", func() {
		sourceID, copyrightID := uint(1), uint(1)
		s.mock.ExpectExec(`DELETE FROM source_copyrights WHERE source_id = \$1 AND copyright_id = \$2`).
			WithArgs(sourceID, copyrightID).
			WillReturnResult(sqlmock.NewResult(1, 1))
		err := s.repo.RemoveCopyrightFromSource(context.Background(), sourceID, copyrightID)
		s.Require().NoError(err)
	})
}