package database

import (
	"context"
	"fmt"
	"reflect"
	"testing"
	"time"

	"git.anthrove.art/Anthrove/otter-space-sdk/v5/pkg/models"
	"git.anthrove.art/Anthrove/otter-space-sdk/v5/test"
	"go.opentelemetry.io/contrib/bridges/otellogrus"
	"go.opentelemetry.io/otel"
	"gorm.io/gorm"
)

func TestCreatePost(t *testing.T) {
	// Setup trow away container
	ctx := context.Background()
	container, gormDB, err := test.StartPostgresContainer(ctx)
	if err != nil {
		logger.Fatalf("Could not start PostgreSQL container: %v", err)
	}

	client = gormDB

	// Setup open telemetry
	tracer = otel.Tracer(tracingName)

	hook := otellogrus.NewHook(tracingName)
	logger.AddHook(hook)

	defer container.Terminate(ctx)

	// -- -- Setup Tests

	// -- Create Post to test with
	validPost := models.Post{
		BaseModel: models.BaseModel[models.PostID]{
			ID:        models.PostID(fmt.Sprintf("%025s", "Post1")),
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
			DeletedAt: gorm.DeletedAt{},
		},
		Rating: models.SFW,
	}
	// --

	// -- -- Tests
	type args struct {
		ctx  context.Context
		post models.Post
	}
	tests := []struct {
		name    string
		args    args
		want    models.Post
		wantErr bool
	}{
		{
			name: "Test 01: Valid Post",
			args: args{
				ctx:  ctx,
				post: validPost,
			},
			want:    validPost,
			wantErr: false,
		},
		{
			name: "Test 02: Duplicate Post",
			args: args{
				ctx:  ctx,
				post: validPost,
			},
			want:    models.Post{},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := CreatePost(tt.args.ctx, tt.args.post)
			if (err != nil) != tt.wantErr {
				t.Errorf("CreatePost() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("CreatePost() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCreatePostInBatch(t *testing.T) {
	// Setup trow away container
	ctx := context.Background()
	container, gormDB, err := test.StartPostgresContainer(ctx)
	if err != nil {
		logger.Fatalf("Could not start PostgreSQL container: %v", err)
	}

	client = gormDB

	// Setup open telemetry
	tracer = otel.Tracer(tracingName)

	hook := otellogrus.NewHook(tracingName)
	logger.AddHook(hook)

	defer container.Terminate(ctx)

	// -- -- Setup Tests

	// -- Create Posts to test with
	validPosts := test.GenerateRandomPosts(20)
	// --

	// -- -- Tests
	type args struct {
		ctx       context.Context
		post      []models.Post
		batchSize int
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			name: "Test 01: Valid Posts",
			args: args{
				ctx:       ctx,
				post:      validPosts,
				batchSize: len(validPosts),
			},
			wantErr: false,
		},
		{
			name: "Test 02: Duplicate Posts",
			args: args{
				ctx:       ctx,
				post:      validPosts,
				batchSize: len(validPosts),
			},
			wantErr: true,
		},
		{
			name: "Test 03: Nil Posts",
			args: args{
				ctx:       ctx,
				post:      nil,
				batchSize: len(validPosts),
			},
			wantErr: true,
		},
		{
			name: "Test 04: Empty Posts",
			args: args{
				ctx:       ctx,
				post:      []models.Post{},
				batchSize: len(validPosts),
			},
			wantErr: true,
		},
		{
			name: "Test 08: Empty Batch Size",
			args: args{
				ctx:       ctx,
				post:      []models.Post{},
				batchSize: 0,
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := CreatePostInBatch(tt.args.ctx, tt.args.post, tt.args.batchSize); (err != nil) != tt.wantErr {
				t.Errorf("CreatePostInBatch() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestGetPostByID(t *testing.T) {
	// Setup trow away container
	ctx := context.Background()
	container, gormDB, err := test.StartPostgresContainer(ctx)
	if err != nil {
		logger.Fatalf("Could not start PostgreSQL container: %v", err)
	}

	client = gormDB

	// Setup open telemetry
	tracer = otel.Tracer(tracingName)

	hook := otellogrus.NewHook(tracingName)
	logger.AddHook(hook)

	defer container.Terminate(ctx)

	// -- -- Setup Tests

	// -- Create Post to test with
	validPost := models.Post{
		BaseModel: models.BaseModel[models.PostID]{
			ID:        models.PostID(fmt.Sprintf("%025s", "Post1")),
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
			DeletedAt: gorm.DeletedAt{},
		},
		Rating: models.SFW,
	}

	validPost, err = CreatePost(ctx, validPost)
	if err != nil {
		logger.Fatal(err)
	}
	// --

	// -- -- Tests
	type args struct {
		ctx context.Context
		id  models.PostID
	}
	tests := []struct {
		name    string
		args    args
		want    models.Post
		wantErr bool
	}{
		{
			name: "Test 01: Valid PostID",
			args: args{
				ctx: ctx,
				id:  validPost.ID,
			},
			want:    validPost,
			wantErr: false,
		},
		{
			name: "Test 03: Empty PostID",
			args: args{
				ctx: ctx,
				id:  "",
			},
			wantErr: true,
		},
		{
			name: "Test 04: Short PostID",
			args: args{
				ctx: ctx,
				id:  "111",
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := GetPostByID(tt.args.ctx, tt.args.id)
			if (err != nil) != tt.wantErr {
				t.Errorf("GetPostByID() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !checkPostID(got, tt.want) {
				t.Errorf("GetPostByID() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestUpdatePost(t *testing.T) {
	// Setup trow away container
	ctx := context.Background()
	container, gormDB, err := test.StartPostgresContainer(ctx)
	if err != nil {
		logger.Fatalf("Could not start PostgreSQL container: %v", err)
	}

	client = gormDB

	// Setup open telemetry
	tracer = otel.Tracer(tracingName)

	hook := otellogrus.NewHook(tracingName)
	logger.AddHook(hook)

	defer container.Terminate(ctx)

	// -- -- Setup Tests

	// -- Create Post to test with
	validPost := models.Post{
		BaseModel: models.BaseModel[models.PostID]{
			ID:        models.PostID(fmt.Sprintf("%025s", "Post1")),
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
			DeletedAt: gorm.DeletedAt{},
		},
		Rating: models.SFW,
	}

	validPost, err = CreatePost(ctx, validPost)
	if err != nil {
		logger.Fatal(err)
	}
	// --

	// -- Create Updates models for Post
	validUpdatePost := validPost
	validUpdatePost.Rating = models.NSFW

	invalidUpdatePost := models.Post{}
	// --

	// -- -- Tests
	type args struct {
		ctx          context.Context
		anthrovePost models.Post
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			name: "Test 01: Valid Update for Post",
			args: args{
				ctx:          ctx,
				anthrovePost: validUpdatePost,
			},
			wantErr: false,
		},
		{
			name: "Test 02: Invalid Update for Post",
			args: args{
				ctx:          ctx,
				anthrovePost: invalidUpdatePost,
			},
			wantErr: true,
		},
		{
			name: "Test 03: Empty ID for Update for Post",
			args: args{
				ctx:          ctx,
				anthrovePost: models.Post{BaseModel: models.BaseModel[models.PostID]{ID: ""}},
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := UpdatePost(tt.args.ctx, tt.args.anthrovePost); (err != nil) != tt.wantErr {
				t.Errorf("UpdatePost() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestDeletePost(t *testing.T) {
	// Setup trow away container
	ctx := context.Background()
	container, gormDB, err := test.StartPostgresContainer(ctx)
	if err != nil {
		logger.Fatalf("Could not start PostgreSQL container: %v", err)
	}

	client = gormDB

	// Setup open telemetry
	tracer = otel.Tracer(tracingName)

	hook := otellogrus.NewHook(tracingName)
	logger.AddHook(hook)

	defer container.Terminate(ctx)

	// -- -- Setup Tests

	// -- Create Post to test with
	validPost := models.Post{
		BaseModel: models.BaseModel[models.PostID]{
			ID:        models.PostID(fmt.Sprintf("%025s", "Post1")),
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
			DeletedAt: gorm.DeletedAt{},
		},
		Rating: models.SFW,
	}

	validPost, err = CreatePost(ctx, validPost)
	if err != nil {
		logger.Fatal(err)
	}
	// --

	// -- -- Tests
	type args struct {
		ctx context.Context
		id  models.PostID
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			name: "Test 01: Delete Valid Post",
			args: args{
				ctx: ctx,
				id:  validPost.ID,
			},
			wantErr: false,
		},
		{
			name: "Test 02: Delete not existed Post",
			args: args{
				ctx: ctx,
				id:  validPost.ID,
			},
			wantErr: false,
		},
		{
			name: "Test 03: Empty PostID",
			args: args{
				ctx: ctx,
				id:  "",
			},
			wantErr: true,
		},
		{
			name: "Test 04: Short PostID",
			args: args{
				ctx: ctx,
				id:  "111",
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := DeletePost(tt.args.ctx, tt.args.id); (err != nil) != tt.wantErr {
				t.Errorf("DeletePost() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func checkPostID(got models.Post, want models.Post) bool {
	if got.ID != want.ID {
		return false
	}

	return true
}