package services_test

import (
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/rede5/gohorsejobs/backend/internal/dto"
	"github.com/rede5/gohorsejobs/backend/internal/services"
	"github.com/stretchr/testify/assert"
)

func TestCreateJob(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
	}
	defer db.Close()

	service := services.NewJobService(db)

	tests := []struct {
		name    string
		req     dto.CreateJobRequest
		mockRun func()
		wantErr bool
	}{
		{
			name: "Success",
			req: dto.CreateJobRequest{
				CompanyID: "1",
				Title:     "Go Developer",
				Status:    "published",
			},
			mockRun: func() {
				mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO jobs`)).
					WithArgs("1", "user-123", "Go Developer", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "published", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()).
					WillReturnRows(sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).AddRow("100", time.Now(), time.Now()))
			},
			wantErr: false,
		},
		{
			name: "DB Error",
			req: dto.CreateJobRequest{
				CompanyID: "1",
				Title:     "Go Developer",
			},
			mockRun: func() {
				mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO jobs`)).
					WillReturnError(assert.AnError)
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockRun()
			got, err := service.CreateJob(tt.req, "user-123")
			if (err != nil) != tt.wantErr {
				t.Errorf("JobService.CreateJob() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !tt.wantErr {
				assert.Equal(t, "100", got.ID)
			}
		})
	}
}

func TestGetJobs(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
	}
	defer db.Close()

	service := services.NewJobService(db)

	tests := []struct {
		name    string
		filter  dto.JobFilterQuery
		mockRun func()
		wantErr bool
	}{
		{
			name: "List All",
			filter: dto.JobFilterQuery{
				PaginationQuery: dto.PaginationQuery{Page: 1, Limit: 10},
			},
			mockRun: func() {
				// List query
				mock.ExpectQuery(regexp.QuoteMeta(`SELECT
                        j.id, j.company_id, j.title, j.description, j.salary_min, j.salary_max, j.salary_type,
                        j.employment_type, j.work_mode, j.working_hours, j.location, j.status, j.salary_negotiable, j.is_featured, j.created_at, j.updated_at,
                        COALESCE(c.name, '') as company_name, c.logo_url as company_logo_url,
                        r.name as region_name, ci.name as city_name
                FROM jobs j`)).
					WillReturnRows(sqlmock.NewRows([]string{
						"id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type",
						"employment_type", "work_mode", "working_hours", "location", "status", "salary_negotiable", "is_featured", "created_at", "updated_at",
						"company_name", "company_logo_url", "region_name", "city_name",
					}).AddRow(
						"1", "10", "Dev", "Desc", 100, 200, "m", "ft", "Remote", "40h", "Remote", "open", true, false, time.Now(), time.Now(),
						"Acme", "url", "Region", "City",
					))

				// Count query
				mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs j WHERE 1=1`)).
					WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(2))
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockRun()
			_, _, err := service.GetJobs(tt.filter)
			if (err != nil) != tt.wantErr {
				t.Errorf("JobService.GetJobs() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
		})
	}
}
func TestGetJobByID(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
	}
	defer db.Close()

	service := services.NewJobService(db)

	jobID := "100"

	// Mocking row for GetJobByID (20 columns)
	rows := sqlmock.NewRows([]string{
		"id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type",
		"employment_type", "working_hours", "location", "region_id", "city_id", "salary_negotiable",
		"requirements", "benefits", "visa_support", "language_level", "status", "created_at", "updated_at",
	}).AddRow(
		jobID, 1, "Title", "Desc", 100, 200, "m",
		"ft", "40h", "Remote", 0, 0, false,
		nil, nil, false, "N2", "open", time.Now(), time.Now(),
	)

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, company_id`)).
		WithArgs(jobID).
		WillReturnRows(rows)

	job, err := service.GetJobByID(jobID)
	if err != nil {
		t.Errorf("GetJobByID() error = %v", err)
		return
	}
	if job == nil || job.ID != jobID {
		t.Errorf("GetJobByID() got = %v, want ID %s", job, jobID)
	}
}

func TestUpdateJob(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
	}
	defer db.Close()

	service := services.NewJobService(db)
	jobID := "100"
	newTitle := "New Title"

	req := dto.UpdateJobRequest{
		Title: &newTitle,
	}

	// Expect UPDATE with RETURNING
	mock.ExpectQuery(regexp.QuoteMeta(`UPDATE jobs SET title = $1, updated_at = NOW() WHERE id = $2 RETURNING id, updated_at`)).
		WithArgs(newTitle, jobID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "updated_at"}).AddRow(jobID, time.Now()))

	// Expect subsequent SELECT to fetch updated job (20 columns)
	rows := sqlmock.NewRows([]string{
		"id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type",
		"employment_type", "working_hours", "location", "region_id", "city_id", "salary_negotiable",
		"requirements", "benefits", "visa_support", "language_level", "status", "created_at", "updated_at",
	}).AddRow(
		jobID, 1, newTitle, "Desc", 100, 200, "m",
		"ft", "40h", "Remote", 0, 0, false,
		nil, nil, false, "N2", "open", time.Now(), time.Now(),
	)

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, company_id`)).
		WithArgs(jobID).
		WillReturnRows(rows)

	job, err := service.UpdateJob(jobID, req)
	if err != nil {
		t.Errorf("UpdateJob() error = %v", err)
		return
	}
	if job.Title != newTitle {
		t.Errorf("UpdateJob() title = %s, want %s", job.Title, newTitle)
	}
}

func TestDeleteJob(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
	}
	defer db.Close()

	service := services.NewJobService(db)
	jobID := "100"

	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM jobs WHERE id = $1`)).
		WithArgs(jobID).
		WillReturnResult(sqlmock.NewResult(1, 1))

	err = service.DeleteJob(jobID)
	if err != nil {
		t.Errorf("DeleteJob() error = %v", err)
	}
}
