package job

import (
	"errors"
	"testing"

	"system-altrak/internal/domain"
)

type jobRepoMock struct {
	createErr      error
	updateErr      error
	getByIDErr     error
	listActiveErr  error
	listAllErr     error
	getByIDJob     *domain.BackgroundJob
	listActiveJobs []domain.BackgroundJob
	listAllJobs    []domain.BackgroundJob
	listAllTotal   int64
	createdJob     *domain.BackgroundJob
	updatedJob     *domain.BackgroundJob
	listAllPage    int
	listAllLimit   int
	listAllBranch  uint
	activeBranch   uint
}

func (m *jobRepoMock) Create(job *domain.BackgroundJob) error {
	m.createdJob = job
	return m.createErr
}

func (m *jobRepoMock) Update(job *domain.BackgroundJob) error {
	m.updatedJob = job
	return m.updateErr
}

func (m *jobRepoMock) GetByID(id uint) (*domain.BackgroundJob, error) {
	if m.getByIDErr != nil {
		return nil, m.getByIDErr
	}
	if m.getByIDJob == nil {
		return nil, errors.New("not found")
	}
	copy := *m.getByIDJob
	return &copy, nil
}

func (m *jobRepoMock) ListActive(branchID uint) ([]domain.BackgroundJob, error) {
	m.activeBranch = branchID
	if m.listActiveErr != nil {
		return nil, m.listActiveErr
	}
	return m.listActiveJobs, nil
}

func (m *jobRepoMock) ListAll(page, limit int, branchID uint) ([]domain.BackgroundJob, int64, error) {
	m.listAllPage = page
	m.listAllLimit = limit
	m.listAllBranch = branchID
	if m.listAllErr != nil {
		return nil, 0, m.listAllErr
	}
	return m.listAllJobs, m.listAllTotal, nil
}

func TestCreateJobSetsDefaultFields(t *testing.T) {
	repo := &jobRepoMock{}
	svc := NewService(repo)

	job, err := svc.CreateJob("IMPORT_PSO", "importing", 50, 2, 99)
	if err != nil {
		t.Fatalf("CreateJob returned error: %v", err)
	}
	if repo.createdJob == nil {
		t.Fatal("expected repository Create to receive job")
	}
	if job.Status != "PENDING" {
		t.Fatalf("expected status PENDING, got %q", job.Status)
	}
	if job.Progress != 0 {
		t.Fatalf("expected progress 0, got %d", job.Progress)
	}
	if job.TotalItems != 50 {
		t.Fatalf("expected total items 50, got %d", job.TotalItems)
	}
	if job.BranchID != 2 || job.CreatedBy != 99 {
		t.Fatalf("expected branch=2 createdBy=99, got branch=%d createdBy=%d", job.BranchID, job.CreatedBy)
	}
}

func TestStartJobUpdatesStatusAndStartedAt(t *testing.T) {
	repo := &jobRepoMock{getByIDJob: &domain.BackgroundJob{Status: "PENDING"}}
	svc := NewService(repo)

	if err := svc.StartJob(1); err != nil {
		t.Fatalf("StartJob returned error: %v", err)
	}
	if repo.updatedJob == nil {
		t.Fatal("expected repository Update to be called")
	}
	if repo.updatedJob.Status != "PROCESSING" {
		t.Fatalf("expected PROCESSING, got %q", repo.updatedJob.Status)
	}
	if repo.updatedJob.StartedAt == nil {
		t.Fatal("expected StartedAt to be set")
	}
}

func TestStartJobPropagatesGetByIDError(t *testing.T) {
	repo := &jobRepoMock{getByIDErr: errors.New("db down")}
	svc := NewService(repo)

	err := svc.StartJob(1)
	if err == nil {
		t.Fatal("expected error from GetByID")
	}
	if repo.updatedJob != nil {
		t.Fatal("did not expect update call when GetByID fails")
	}
}

func TestUpdateProgressCalculatesProgressAndUpdatesDescription(t *testing.T) {
	repo := &jobRepoMock{getByIDJob: &domain.BackgroundJob{TotalItems: 200, Progress: 0, Description: "old"}}
	svc := NewService(repo)

	if err := svc.UpdateProgress(7, 50, "step 1"); err != nil {
		t.Fatalf("UpdateProgress returned error: %v", err)
	}
	if repo.updatedJob == nil {
		t.Fatal("expected repository Update to be called")
	}
	if repo.updatedJob.Processed != 50 {
		t.Fatalf("expected processed=50, got %d", repo.updatedJob.Processed)
	}
	if repo.updatedJob.Progress != 25 {
		t.Fatalf("expected progress=25, got %d", repo.updatedJob.Progress)
	}
	if repo.updatedJob.Description != "step 1" {
		t.Fatalf("expected description updated, got %q", repo.updatedJob.Description)
	}
}

func TestUpdateProgressKeepsProgressWhenTotalItemsZero(t *testing.T) {
	repo := &jobRepoMock{getByIDJob: &domain.BackgroundJob{TotalItems: 0, Progress: 77, Description: "keep"}}
	svc := NewService(repo)

	if err := svc.UpdateProgress(7, 10, ""); err != nil {
		t.Fatalf("UpdateProgress returned error: %v", err)
	}
	if repo.updatedJob == nil {
		t.Fatal("expected repository Update to be called")
	}
	if repo.updatedJob.Progress != 77 {
		t.Fatalf("expected progress to remain 77, got %d", repo.updatedJob.Progress)
	}
	if repo.updatedJob.Description != "keep" {
		t.Fatalf("expected description to remain unchanged, got %q", repo.updatedJob.Description)
	}
}

func TestCompleteJobSetsCompletedState(t *testing.T) {
	repo := &jobRepoMock{getByIDJob: &domain.BackgroundJob{Status: "PROCESSING", Progress: 30}}
	svc := NewService(repo)

	if err := svc.CompleteJob(9); err != nil {
		t.Fatalf("CompleteJob returned error: %v", err)
	}
	if repo.updatedJob == nil {
		t.Fatal("expected repository Update to be called")
	}
	if repo.updatedJob.Status != "COMPLETED" {
		t.Fatalf("expected COMPLETED, got %q", repo.updatedJob.Status)
	}
	if repo.updatedJob.Progress != 100 {
		t.Fatalf("expected progress=100, got %d", repo.updatedJob.Progress)
	}
	if repo.updatedJob.CompletedAt == nil {
		t.Fatal("expected CompletedAt to be set")
	}
}

func TestFailJobSetsFailedState(t *testing.T) {
	repo := &jobRepoMock{getByIDJob: &domain.BackgroundJob{Status: "PROCESSING"}}
	svc := NewService(repo)

	if err := svc.FailJob(9, "boom"); err != nil {
		t.Fatalf("FailJob returned error: %v", err)
	}
	if repo.updatedJob == nil {
		t.Fatal("expected repository Update to be called")
	}
	if repo.updatedJob.Status != "FAILED" {
		t.Fatalf("expected FAILED, got %q", repo.updatedJob.Status)
	}
	if repo.updatedJob.ErrorMessage != "boom" {
		t.Fatalf("expected error message boom, got %q", repo.updatedJob.ErrorMessage)
	}
	if repo.updatedJob.CompletedAt == nil {
		t.Fatal("expected CompletedAt to be set")
	}
}

func TestGetActiveJobsDelegatesToRepository(t *testing.T) {
	expected := []domain.BackgroundJob{{JobType: "IMPORT_PSO", Status: "PENDING"}}
	repo := &jobRepoMock{listActiveJobs: expected}
	svc := NewService(repo)

	jobs, err := svc.GetActiveJobs(3)
	if err != nil {
		t.Fatalf("GetActiveJobs returned error: %v", err)
	}
	if repo.activeBranch != 3 {
		t.Fatalf("expected branch 3 forwarded to repo, got %d", repo.activeBranch)
	}
	if len(jobs) != 1 || jobs[0].JobType != "IMPORT_PSO" {
		t.Fatalf("unexpected active jobs result: %+v", jobs)
	}
}

func TestListJobsDelegatesPaging(t *testing.T) {
	repo := &jobRepoMock{
		listAllJobs:  []domain.BackgroundJob{{JobType: "BACKUP_DB", Status: "COMPLETED"}},
		listAllTotal: 11,
	}
	svc := NewService(repo)

	jobs, total, err := svc.ListJobs(2, 25, 4)
	if err != nil {
		t.Fatalf("ListJobs returned error: %v", err)
	}
	if repo.listAllPage != 2 || repo.listAllLimit != 25 || repo.listAllBranch != 4 {
		t.Fatalf("unexpected paging args forwarded: page=%d limit=%d branch=%d", repo.listAllPage, repo.listAllLimit, repo.listAllBranch)
	}
	if total != 11 {
		t.Fatalf("expected total=11, got %d", total)
	}
	if len(jobs) != 1 || jobs[0].JobType != "BACKUP_DB" {
		t.Fatalf("unexpected jobs payload: %+v", jobs)
	}
}
