package handlers

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"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) (*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) (*models.Job, error) {
	if m.createJobFunc != nil {
		return m.createJobFunc(req)
	}
	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
}

// JobServiceInterface defines the interface for job service operations
type JobServiceInterface interface {
	GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error)
	CreateJob(req dto.CreateJobRequest) (*models.Job, error)
	GetJobByID(id string) (*models.Job, error)
	UpdateJob(id string, req dto.UpdateJobRequest) (*models.Job, error)
	DeleteJob(id string) error
}

// testableJobHandler wraps an interface for testing
type testableJobHandler struct {
	service JobServiceInterface
}

func newTestableJobHandler(service JobServiceInterface) *testableJobHandler {
	return &testableJobHandler{service: service}
}

func (h *testableJobHandler) GetJobs(w http.ResponseWriter, r *http.Request) {
	jobs, total, err := h.service.GetJobs(dto.JobFilterQuery{})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	response := dto.PaginatedResponse{
		Data: jobs,
		Pagination: dto.Pagination{
			Total: total,
		},
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

func (h *testableJobHandler) CreateJob(w http.ResponseWriter, r *http.Request) {
	var req dto.CreateJobRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	job, err := h.service.CreateJob(req)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(job)
}

func (h *testableJobHandler) GetJobByID(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	job, err := h.service.GetJobByID(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(job)
}

func (h *testableJobHandler) DeleteJob(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	if err := h.service.DeleteJob(id); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// =============================================================================
// Test Cases
// =============================================================================

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",
						CompanyID: "1",
						Title:     "Software Engineer",
						Status:    "open",
						CreatedAt: time.Now(),
						UpdatedAt: time.Now(),
					},
					CompanyName: "TestCorp",
				},
				{
					Job: models.Job{
						ID:        "2",
						CompanyID: "1",
						Title:     "DevOps Engineer",
						Status:    "open",
						CreatedAt: time.Now(),
						UpdatedAt: time.Now(),
					},
					CompanyName: "TestCorp",
				},
			}, 2, nil
		},
	}

	handler := newTestableJobHandler(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 TestGetJobs_Empty(t *testing.T) {
	mockService := &mockJobService{
		getJobsFunc: func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) {
			return []models.JobWithCompany{}, 0, nil
		},
	}

	handler := newTestableJobHandler(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, 0, response.Pagination.Total)
}

func TestGetJobs_Error(t *testing.T) {
	mockService := &mockJobService{
		getJobsFunc: func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) {
			return nil, 0, assert.AnError
		},
	}

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

	handler.GetJobs(rr, req)

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

func TestCreateJob_Success(t *testing.T) {
	mockService := &mockJobService{
		createJobFunc: func(req dto.CreateJobRequest) (*models.Job, error) {
			return &models.Job{
				ID:          "1",
				CompanyID:   req.CompanyID,
				Title:       req.Title,
				Description: req.Description,
				Status:      req.Status,
				CreatedAt:   time.Now(),
				UpdatedAt:   time.Now(),
			}, nil
		},
	}

	handler := newTestableJobHandler(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")
	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_InvalidJSON(t *testing.T) {
	mockService := &mockJobService{}

	handler := newTestableJobHandler(mockService)

	req := httptest.NewRequest("POST", "/jobs", bytes.NewReader([]byte("invalid json")))
	req.Header.Set("Content-Type", "application/json")
	rr := httptest.NewRecorder()

	handler.CreateJob(rr, req)

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

func TestCreateJob_ServiceError(t *testing.T) {
	mockService := &mockJobService{
		createJobFunc: func(req dto.CreateJobRequest) (*models.Job, error) {
			return nil, assert.AnError
		},
	}

	handler := newTestableJobHandler(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")
	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,
				CompanyID:   "1",
				Title:       "Software Engineer",
				Description: "Great job opportunity",
				Status:      "open",
				CreatedAt:   time.Now(),
				UpdatedAt:   time.Now(),
			}, nil
		},
	}

	handler := newTestableJobHandler(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)

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

func TestGetJobByID_NotFound(t *testing.T) {
	mockService := &mockJobService{
		getJobByIDFunc: func(id string) (*models.Job, error) {
			return nil, assert.AnError
		},
	}

	handler := newTestableJobHandler(mockService)

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

	handler.GetJobByID(rr, req)

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

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

	handler := newTestableJobHandler(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 TestDeleteJob_Error(t *testing.T) {
	mockService := &mockJobService{
		deleteJobFunc: func(id string) error {
			return assert.AnError
		},
	}

	handler := newTestableJobHandler(mockService)

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

	handler.DeleteJob(rr, req)

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