//go:build e2e
// +build e2e

package e2e

import (
	"fmt"
	"io"
	"net/http"
	"testing"
	"time"

	"github.com/rede5/gohorsejobs/backend/internal/database"
	"github.com/rede5/gohorsejobs/backend/internal/dto"
	"github.com/rede5/gohorsejobs/backend/internal/models"

	"os"

	"github.com/golang-jwt/jwt/v5"
)

// createAuthToken generates a JWT token for testing
func createAuthToken(t *testing.T, userID, tenantID string) string {
	t.Helper()
	secret := os.Getenv("JWT_SECRET")
	if secret == "" {
		secret = "gohorse-super-secret-key-2024-production"
	}
	claims := jwt.MapClaims{
		"sub":    userID,
		"tenant": tenantID,
		"roles":  []string{"superadmin"},
		"iss":    "gohorse-jobs",
		"exp":    time.Now().Add(time.Hour).Unix(),
		"iat":    time.Now().Unix(),
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenStr, err := token.SignedString([]byte(secret))
	if err != nil {
		t.Fatalf("Failed to generate token: %v", err)
	}
	return tokenStr
}

// setupTestCompanyAndUser creates a test company and user in the database and returns their IDs
func setupTestCompanyAndUser(t *testing.T) (companyID, userID string) {
	t.Helper()

	// Create user first (required for created_by in jobs)
	userQuery := `
		INSERT INTO users (identifier, password_hash, role, full_name, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6)
		ON CONFLICT (identifier) DO UPDATE SET password_hash = $2
		RETURNING id
	`
	err := database.DB.QueryRow(
		userQuery,
		"e2e-test-jobs-user",
		"hashedpassword",
		"superadmin",
		"E2E Test Jobs User",
		time.Now(),
		time.Now(),
	).Scan(&userID)

	if err != nil {
		t.Fatalf("Failed to create test user: %v", err)
	}

	// Create company
	companyQuery := `
		INSERT INTO companies (name, slug, type, active, verified, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
		ON CONFLICT (slug) DO UPDATE SET name = $1
		RETURNING id
	`
	err = database.DB.QueryRow(
		companyQuery,
		"E2E Test Company",
		"e2e-test-company",
		"employer",
		true,
		false,
		time.Now(),
		time.Now(),
	).Scan(&companyID)

	if err != nil {
		t.Fatalf("Failed to create test company: %v", err)
	}

	return companyID, userID
}

// cleanupTestCompanyAndUser removes the test company and user
func cleanupTestCompanyAndUser(t *testing.T, companyID, userID string) {
	t.Helper()
	database.DB.Exec("DELETE FROM applications WHERE job_id IN (SELECT id FROM jobs WHERE company_id = $1)", companyID)
	database.DB.Exec("DELETE FROM jobs WHERE company_id = $1", companyID)
	database.DB.Exec("DELETE FROM companies WHERE id = $1", companyID)
	// Don't delete user as it might be used elsewhere
}

// createTestJob creates a job directly in the database (bypasses API auth requirement)
func createTestJob(t *testing.T, companyID, userID string, title string) string {
	t.Helper()

	query := `
		INSERT INTO jobs (company_id, created_by, title, description, status, visa_support, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING id
	`
	var jobID string
	err := database.DB.QueryRow(
		query,
		companyID,
		userID,
		title,
		"Test job created by E2E tests",
		"open",
		false,
		time.Now(),
		time.Now(),
	).Scan(&jobID)

	if err != nil {
		t.Fatalf("Failed to create test job: %v", err)
	}

	return jobID
}

// TestE2E_Jobs_Read tests job reading operations
func TestE2E_Jobs_Read(t *testing.T) {
	client := newTestClient()
	companyID, userID := setupTestCompanyAndUser(t)
	defer cleanupTestCompanyAndUser(t, companyID, userID)

	// Create a test job directly in DB
	jobID := createTestJob(t, companyID, userID, "E2E Test Software Engineer")
	defer database.DB.Exec("DELETE FROM jobs WHERE id = $1", jobID)

	// =====================
	// 0. CREATE JOB (POST)
	// =====================
	t.Run("CreateJob", func(t *testing.T) {
		// Generate token for auth
		token := createAuthToken(t, userID, companyID)
		client.setAuthToken(token)

		title := "New Created Job E2E"
		desc := "This is a new job created via API E2E test."
		salaryMin := 5000.0
		salaryMax := 8000.0
		salaryType := "monthly"
		employmentType := "full-time"
		// workMode := "remote"
		location := "Sao Paulo, SP"

		createReq := dto.CreateJobRequest{
			CompanyID:      companyID,
			Title:          title,
			Description:    desc,
			SalaryMin:      &salaryMin,
			SalaryMax:      &salaryMax,
			SalaryType:     &salaryType,
			EmploymentType: &employmentType,
			WorkingHours:   nil,
			Location:       &location,
			// WorkMode:       &workMode, // Temporarily removed as DTO doesn't support it yet
			Status: "open",
		}

		resp, err := client.post("/api/v1/jobs", createReq)
		if err != nil {
			t.Fatalf("Failed to create job: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusCreated {
			body, _ := io.ReadAll(resp.Body)
			t.Errorf("Expected status 201, got %d. Body: %s", resp.StatusCode, string(body))
		}

		var job models.Job
		if err := parseJSON(resp, &job); err != nil {
			t.Fatalf("Failed to parse response: %v", err)
		}

		if job.Title != title {
			t.Errorf("Expected title '%s', got '%s'", title, job.Title)
		}

		// Cleanup created job
		database.DB.Exec("DELETE FROM jobs WHERE id = $1", job.ID)
	})

	// =====================
	// 1. GET JOB BY ID
	// =====================
	t.Run("GetJobByID", func(t *testing.T) {
		resp, err := client.get(fmt.Sprintf("/api/v1/jobs/%s", jobID))
		if err != nil {
			t.Fatalf("Failed to get job: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Expected status 200, got %d", resp.StatusCode)
		}

		var job models.Job
		if err := parseJSON(resp, &job); err != nil {
			t.Fatalf("Failed to parse response: %v", err)
		}

		if job.ID != jobID {
			t.Errorf("Expected job ID %s, got %s", jobID, job.ID)
		}

		if job.Title != "E2E Test Software Engineer" {
			t.Errorf("Expected title 'E2E Test Software Engineer', got '%s'", job.Title)
		}
	})

	// =====================
	// 2. LIST JOBS
	// =====================
	t.Run("ListJobs", func(t *testing.T) {
		resp, err := client.get("/api/v1/jobs")
		if err != nil {
			t.Fatalf("Failed to list jobs: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Expected status 200, got %d", resp.StatusCode)
		}

		var response dto.PaginatedResponse
		if err := parseJSON(resp, &response); err != nil {
			t.Fatalf("Failed to parse response: %v", err)
		}

		// Should have at least our created job
		if response.Pagination.Total < 1 {
			t.Error("Expected at least 1 job in list")
		}
	})
}

// TestE2E_Jobs_Update tests job update operations
func TestE2E_Jobs_Update(t *testing.T) {
	client := newTestClient()
	companyID, userID := setupTestCompanyAndUser(t)
	defer cleanupTestCompanyAndUser(t, companyID, userID)

	// Generate and set auth token
	token := createAuthToken(t, userID, companyID)
	client.setAuthToken(token)

	// Create a test job
	jobID := createTestJob(t, companyID, userID, "E2E Test Job for Update")
	defer database.DB.Exec("DELETE FROM jobs WHERE id = $1", jobID)

	t.Run("UpdateJobTitle", func(t *testing.T) {
		newTitle := "E2E Test Updated Title"
		updateReq := dto.UpdateJobRequest{
			Title: &newTitle,
		}

		resp, err := client.put(fmt.Sprintf("/api/v1/jobs/%s", jobID), updateReq)
		if err != nil {
			t.Fatalf("Failed to update job: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Expected status 200, got %d", resp.StatusCode)
		}

		var job models.Job
		if err := parseJSON(resp, &job); err != nil {
			t.Fatalf("Failed to parse response: %v", err)
		}

		if job.Title != newTitle {
			t.Errorf("Expected title '%s', got '%s'", newTitle, job.Title)
		}
	})

	t.Run("UpdateJobStatus", func(t *testing.T) {
		newStatus := "closed"
		updateReq := dto.UpdateJobRequest{
			Status: &newStatus,
		}

		resp, err := client.put(fmt.Sprintf("/api/v1/jobs/%s", jobID), updateReq)
		if err != nil {
			t.Fatalf("Failed to update job: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Expected status 200, got %d", resp.StatusCode)
		}
	})
}

// TestE2E_Jobs_Delete tests job deletion
func TestE2E_Jobs_Delete(t *testing.T) {
	client := newTestClient()
	companyID, userID := setupTestCompanyAndUser(t)
	defer cleanupTestCompanyAndUser(t, companyID, userID)

	// Generate and set auth token
	token := createAuthToken(t, userID, companyID)
	client.setAuthToken(token)

	// Create a test job to delete
	jobID := createTestJob(t, companyID, userID, "E2E Test Job to Delete")

	t.Run("DeleteJob", func(t *testing.T) {
		resp, err := client.delete(fmt.Sprintf("/api/v1/jobs/%s", jobID))
		if err != nil {
			t.Fatalf("Failed to delete job: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusNoContent {
			t.Errorf("Expected status 204, got %d", resp.StatusCode)
		}

		// Verify job is deleted
		verifyResp, _ := client.get(fmt.Sprintf("/api/v1/jobs/%s", jobID))
		if verifyResp.StatusCode != http.StatusNotFound {
			t.Error("Job should be deleted but still exists")
		}
		verifyResp.Body.Close()
	})
}

// TestE2E_Jobs_Filters tests job listing with filters
func TestE2E_Jobs_Filters(t *testing.T) {
	client := newTestClient()
	companyID, userID := setupTestCompanyAndUser(t)
	defer cleanupTestCompanyAndUser(t, companyID, userID)

	// Create multiple jobs directly in DB
	for i := 1; i <= 3; i++ {
		createTestJob(t, companyID, userID, fmt.Sprintf("E2E Test Job %d", i))
	}

	t.Run("Pagination", func(t *testing.T) {
		resp, err := client.get("/api/v1/jobs?page=1&limit=2")
		if err != nil {
			t.Fatalf("Failed to list jobs: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Expected status 200, got %d", resp.StatusCode)
		}
	})

	t.Run("FilterByCompany", func(t *testing.T) {
		resp, err := client.get(fmt.Sprintf("/api/v1/jobs?companyId=%s", companyID))
		if err != nil {
			t.Fatalf("Failed to list jobs: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Expected status 200, got %d", resp.StatusCode)
		}

		var response dto.PaginatedResponse
		if err := parseJSON(resp, &response); err != nil {
			t.Fatalf("Failed to parse response: %v", err)
		}

		// Should have our 3 test jobs
		if response.Pagination.Total < 3 {
			t.Errorf("Expected at least 3 jobs for company, got %d", response.Pagination.Total)
		}
	})

	t.Run("FilterByFeatured", func(t *testing.T) {
		resp, err := client.get("/api/v1/jobs?featured=true&limit=10")
		if err != nil {
			t.Fatalf("Failed to list jobs: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("Expected status 200, got %d", resp.StatusCode)
		}
	})
}

// TestE2E_Jobs_InvalidInput tests error handling for invalid input
func TestE2E_Jobs_InvalidInput(t *testing.T) {
	client := newTestClient()

	t.Run("GetNonExistentJob", func(t *testing.T) {
		resp, err := client.get("/api/v1/jobs/999999")
		if err != nil {
			t.Fatalf("Failed to make request: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusNotFound {
			t.Errorf("Expected status 404, got %d", resp.StatusCode)
		}
	})

	t.Run("GetInvalidJobID", func(t *testing.T) {
		resp, err := client.get("/api/v1/jobs/invalid")
		if err != nil {
			t.Fatalf("Failed to make request: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusNotFound {
			t.Errorf("Expected status 404, got %d", resp.StatusCode)
		}
	})

	t.Run("CreateJobInvalidJSON", func(t *testing.T) {
		// Need auth for POST
		companyID, userID := setupTestCompanyAndUser(t)
		defer cleanupTestCompanyAndUser(t, companyID, userID)
		token := createAuthToken(t, userID, companyID)

		req, _ := http.NewRequest("POST", testServer.URL+"/api/v1/jobs", nil)
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+token)
		req.Body = http.NoBody

		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("Failed to make request: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusBadRequest {
			t.Errorf("Expected status 400, got %d", resp.StatusCode)
		}
	})
}
