package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/rede5/gohorsejobs/backend/internal/api/middleware"
	"github.com/rede5/gohorsejobs/backend/internal/dto"
	"github.com/rede5/gohorsejobs/backend/internal/models"
	"github.com/stretchr/testify/assert"
)

// mockJobService is a mock implementation of the job service for testing
type mockJobService struct {
	getJobsFunc    func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error)
	createJobFunc  func(req dto.CreateJobRequest, createdBy string) (*models.Job, error)
	getJobByIDFunc func(id string) (*models.Job, error)
	updateJobFunc  func(id string, req dto.UpdateJobRequest) (*models.Job, error)
	deleteJobFunc  func(id string) error
}

func (m *mockJobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) {
	if m.getJobsFunc != nil {
		return m.getJobsFunc(filter)
	}
	return nil, 0, nil
}

func (m *mockJobService) CreateJob(req dto.CreateJobRequest, createdBy string) (*models.Job, error) {
	if m.createJobFunc != nil {
		return m.createJobFunc(req, createdBy)
	}
	return nil, nil
}

func (m *mockJobService) GetJobByID(id string) (*models.Job, error) {
	if m.getJobByIDFunc != nil {
		return m.getJobByIDFunc(id)
	}
	return nil, nil
}

func (m *mockJobService) UpdateJob(id string, req dto.UpdateJobRequest) (*models.Job, error) {
	if m.updateJobFunc != nil {
		return m.updateJobFunc(id, req)
	}
	return nil, nil
}

func (m *mockJobService) DeleteJob(id string) error {
	if m.deleteJobFunc != nil {
		return m.deleteJobFunc(id)
	}
	return nil
}

func TestGetJobs_Success(t *testing.T) {
	mockService := &mockJobService{
		getJobsFunc: func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) {
			return []models.JobWithCompany{
				{
					Job:         models.Job{ID: "1", Title: "Software Engineer", Status: "open"},
					CompanyName: "TestCorp",
				},
				{
					Job:         models.Job{ID: "2", Title: "DevOps", Status: "open"},
					CompanyName: "TestCorp",
				},
			}, 2, nil
		},
	}

	handler := NewJobHandler(mockService)
	req := httptest.NewRequest("GET", "/jobs", nil)
	rr := httptest.NewRecorder()

	handler.GetJobs(rr, req)

	assert.Equal(t, http.StatusOK, rr.Code)

	var response dto.PaginatedResponse
	err := json.Unmarshal(rr.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.Equal(t, 2, response.Pagination.Total)
}

func TestCreateJob_Success(t *testing.T) {
	mockService := &mockJobService{
		createJobFunc: func(req dto.CreateJobRequest, createdBy string) (*models.Job, error) {
			assert.Equal(t, "user-123", createdBy)
			return &models.Job{
				ID:     "1",
				Title:  req.Title,
				Status: "open",
			}, nil
		},
	}

	handler := NewJobHandler(mockService)

	jobReq := dto.CreateJobRequest{
		CompanyID:   "1",
		Title:       "Backend Developer",
		Description: "Build awesome APIs",
		Status:      "open",
	}
	body, _ := json.Marshal(jobReq)

	req := httptest.NewRequest("POST", "/jobs", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")

	// Inject Context
	ctx := context.WithValue(req.Context(), middleware.ContextUserID, "user-123")
	req = req.WithContext(ctx)

	rr := httptest.NewRecorder()

	handler.CreateJob(rr, req)

	assert.Equal(t, http.StatusCreated, rr.Code)

	var job models.Job
	err := json.Unmarshal(rr.Body.Bytes(), &job)
	assert.NoError(t, err)
	assert.Equal(t, "Backend Developer", job.Title)
}

func TestCreateJob_ServiceError(t *testing.T) {
	mockService := &mockJobService{
		createJobFunc: func(req dto.CreateJobRequest, createdBy string) (*models.Job, error) {
			return nil, errors.New("db error")
		},
	}

	handler := NewJobHandler(mockService)

	jobReq := dto.CreateJobRequest{
		Title: "Failing Job",
	}
	body, _ := json.Marshal(jobReq)

	req := httptest.NewRequest("POST", "/jobs", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	ctx := context.WithValue(req.Context(), middleware.ContextUserID, "user-123")
	req = req.WithContext(ctx)

	rr := httptest.NewRecorder()

	handler.CreateJob(rr, req)

	assert.Equal(t, http.StatusInternalServerError, rr.Code)
}

func TestGetJobByID_Success(t *testing.T) {
	mockService := &mockJobService{
		getJobByIDFunc: func(id string) (*models.Job, error) {
			return &models.Job{ID: id, Title: "Software Engineer"}, nil
		},
	}

	handler := NewJobHandler(mockService)

	req := httptest.NewRequest("GET", "/jobs/1", nil)
	req.SetPathValue("id", "1")
	rr := httptest.NewRecorder()

	handler.GetJobByID(rr, req)

	assert.Equal(t, http.StatusOK, rr.Code)
}

func TestDeleteJob_Success(t *testing.T) {
	mockService := &mockJobService{
		deleteJobFunc: func(id string) error {
			return nil
		},
	}

	handler := NewJobHandler(mockService)

	req := httptest.NewRequest("DELETE", "/jobs/1", nil)
	req.SetPathValue("id", "1")
	rr := httptest.NewRecorder()

	handler.DeleteJob(rr, req)

	assert.Equal(t, http.StatusNoContent, rr.Code)
}

func TestUpdateJob_Success(t *testing.T) {
	mockService := &mockJobService{
		updateJobFunc: func(id string, req dto.UpdateJobRequest) (*models.Job, error) {
			return &models.Job{ID: id, Title: "Updated"}, nil
		},
	}
	handler := NewJobHandler(mockService)

	reqBody, _ := json.Marshal(dto.UpdateJobRequest{Title: func() *string { s := "Updated"; return &s }()}) // Inline pointer helper not clean but works or define var

	// Cleaner
	title := "Updated"
	reqBody, _ = json.Marshal(dto.UpdateJobRequest{Title: &title})

	req := httptest.NewRequest("PUT", "/jobs/1", bytes.NewReader(reqBody))
	req.SetPathValue("id", "1")
	rr := httptest.NewRecorder()

	handler.UpdateJob(rr, req)
	assert.Equal(t, http.StatusOK, rr.Code)
}
