package handlers_test

import (
	"bytes"
	"context"
	"database/sql"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/rede5/gohorsejobs/backend/internal/api/handlers"
	"github.com/rede5/gohorsejobs/backend/internal/api/middleware"
	"github.com/rede5/gohorsejobs/backend/internal/core/domain/entity"
	"github.com/rede5/gohorsejobs/backend/internal/core/dto"
	auth "github.com/rede5/gohorsejobs/backend/internal/core/usecases/auth"
	tenant "github.com/rede5/gohorsejobs/backend/internal/core/usecases/tenant"
	user "github.com/rede5/gohorsejobs/backend/internal/core/usecases/user"
	"github.com/rede5/gohorsejobs/backend/internal/services"
)

// --- Mock Implementations ---

type mockUserRepo struct {
	saveFunc        func(user *entity.User) (*entity.User, error)
	findByEmailFunc func(email string) (*entity.User, error)
}

func (m *mockUserRepo) Save(ctx context.Context, user *entity.User) (*entity.User, error) {
	if m.saveFunc != nil {
		return m.saveFunc(user)
	}
	return user, nil
}

func (m *mockUserRepo) FindByEmail(ctx context.Context, email string) (*entity.User, error) {
	if m.findByEmailFunc != nil {
		return m.findByEmailFunc(email)
	}
	return nil, nil
}

func (m *mockUserRepo) FindByID(ctx context.Context, id string) (*entity.User, error) {
	return nil, nil
}

func (m *mockUserRepo) FindAllByTenant(ctx context.Context, tenantID string, l, o int) ([]*entity.User, int, error) {
	return nil, 0, nil
}
func (m *mockUserRepo) Update(ctx context.Context, user *entity.User) (*entity.User, error) {
	return nil, nil
}
func (m *mockUserRepo) Delete(ctx context.Context, id string) error { return nil }
func (m *mockUserRepo) LinkGuestApplications(ctx context.Context, email string, userID string) error {
	return nil
}

type mockAuthService struct{}

func (m *mockAuthService) HashPassword(password string) (string, error) {
	return "hashed_" + password, nil
}
func (m *mockAuthService) GenerateToken(userID, tenantID string, roles []string) (string, error) {
	return "mock_token", nil
}
func (m *mockAuthService) VerifyPassword(hash, password string) bool { return true }
func (m *mockAuthService) ValidateToken(token string) (map[string]interface{}, error) {
	return nil, nil
}

// --- Test Cases ---

func TestRegisterCandidateHandler_Success(t *testing.T) {
	t.Skip("Integration test requires full DI setup")
}

func TestRegisterCandidateHandler_InvalidPayload(t *testing.T) {
	coreHandlers := createTestCoreHandlers(t, nil, nil)

	body := bytes.NewBufferString("{invalid json}")
	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/register", body)
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()

	coreHandlers.RegisterCandidate(rec, req)

	if rec.Code != http.StatusBadRequest {
		t.Errorf("Expected status %d, got %d", http.StatusBadRequest, rec.Code)
	}
}

func TestRegisterCandidateHandler_MissingFields(t *testing.T) {
	coreHandlers := createTestCoreHandlers(t, nil, nil)

	testCases := []struct {
		name     string
		payload  dto.RegisterCandidateRequest
		wantCode int
	}{
		{
			name:     "Missing Email",
			payload:  dto.RegisterCandidateRequest{Name: "John", Password: "123456"},
			wantCode: http.StatusBadRequest,
		},
		{
			name:     "Missing Password",
			payload:  dto.RegisterCandidateRequest{Name: "John", Email: "john@example.com"},
			wantCode: http.StatusBadRequest,
		},
		{
			name:     "Missing Name",
			payload:  dto.RegisterCandidateRequest{Email: "john@example.com", Password: "123456"},
			wantCode: http.StatusBadRequest,
		},
		{
			name:     "All Empty",
			payload:  dto.RegisterCandidateRequest{},
			wantCode: http.StatusBadRequest,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			body, _ := json.Marshal(tc.payload)
			req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/register", bytes.NewBuffer(body))
			req.Header.Set("Content-Type", "application/json")
			rec := httptest.NewRecorder()

			coreHandlers.RegisterCandidate(rec, req)

			if rec.Code != tc.wantCode {
				t.Errorf("Expected status %d, got %d", tc.wantCode, rec.Code)
			}
		})
	}
}

func TestLoginHandler_InvalidPayload(t *testing.T) {
	coreHandlers := createTestCoreHandlers(t, nil, nil)

	body := bytes.NewBufferString("{invalid}")
	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", body)
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()

	coreHandlers.Login(rec, req)

	if rec.Code != http.StatusBadRequest {
		t.Errorf("Expected status %d, got %d", http.StatusBadRequest, rec.Code)
	}
}

func TestLoginHandler_Success(t *testing.T) {
	// Mocks
	mockRepo := &mockUserRepo{
		findByEmailFunc: func(email string) (*entity.User, error) {
			if email == "john@example.com" {
				// Return entity.User
				u := entity.NewUser("u1", "t1", "John", "john@example.com")
				u.PasswordHash = "hashed_123456"
				// Add Role if needed (mocked)
				// u.Roles = ...
				return u, nil
			}
			return nil, nil // Not found
		},
	}
	mockAuth := &mockAuthService{}

	// Real UseCase with Mocks
	loginUC := auth.NewLoginUseCase(mockRepo, mockAuth)

	coreHandlers := createTestCoreHandlers(t, nil, loginUC)

	// Request
	payload := dto.LoginRequest{Email: "john@example.com", Password: "123456"}
	body, _ := json.Marshal(payload)
	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()

	coreHandlers.Login(rec, req)

	// Assert Response Code
	if rec.Code != http.StatusOK {
		t.Errorf("Expected status %d, got %d. Body: %s", http.StatusOK, rec.Code, rec.Body.String())
	}

	// Assert Cookie
	cookies := rec.Result().Cookies()
	var jwtCookie *http.Cookie
	for _, c := range cookies {
		if c.Name == "jwt" {
			jwtCookie = c
			break
		}
	}

	if jwtCookie == nil {
		t.Fatal("Expected jwt cookie not found")
	}
	if !jwtCookie.HttpOnly {
		t.Error("Cookie should be HttpOnly")
	}
	if jwtCookie.Value != "mock_token" {
		t.Errorf("Expected cookie value 'mock_token', got '%s'", jwtCookie.Value)
	}
}

// createTestCoreHandlers creates handlers with mocks and optional DB
func createTestCoreHandlers(t *testing.T, db *sql.DB, loginUC *auth.LoginUseCase) *handlers.CoreHandlers {
	t.Helper()

	// Init services if DB provided
	var auditSvc *services.AuditService
	var notifSvc *services.NotificationService
	var ticketSvc *services.TicketService
	var adminSvc *services.AdminService
	var credSvc *services.CredentialsService

	if db != nil {
		auditSvc = services.NewAuditService(db)
		notifSvc = services.NewNotificationService(db, nil)
		ticketSvc = services.NewTicketService(db)
		adminSvc = services.NewAdminService(db)
		credSvc = services.NewCredentialsService(db)
	}

	return handlers.NewCoreHandlers(
		loginUC,
		(*auth.RegisterCandidateUseCase)(nil),
		(*tenant.CreateCompanyUseCase)(nil),
		(*user.CreateUserUseCase)(nil),
		(*user.ListUsersUseCase)(nil),
		(*user.DeleteUserUseCase)(nil),
		(*user.UpdateUserUseCase)(nil),
		(*user.UpdatePasswordUseCase)(nil),
		(*tenant.ListCompaniesUseCase)(nil),
		auditSvc,
		notifSvc,
		ticketSvc,
		adminSvc,
		credSvc,
	)
}

func TestCoreHandlers_ListNotifications(t *testing.T) {
	// Setup DB Mock
	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()

	// Setup Handlers with DB
	handlers := createTestCoreHandlers(t, db, nil)

	// User ID
	userID := "user-123"

	// Mock DB Query for ListNotifications
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, user_id, type, title, message, link, read_at, created_at, updated_at FROM notifications`)).
		WithArgs(userID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "type", "title", "message", "link", "read_at", "created_at", "updated_at"}).
			AddRow("1", userID, "info", "Welcome", "Hello", nil, nil, time.Now(), time.Now()))

	// Request
	req := httptest.NewRequest(http.MethodGet, "/api/v1/notifications", nil)

	// Inject Context
	ctx := context.WithValue(req.Context(), middleware.ContextUserID, userID)
	req = req.WithContext(ctx)

	rec := httptest.NewRecorder()

	// Execute
	handlers.ListNotifications(rec, req)

	// Assert
	if rec.Code != http.StatusOK {
		t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
	}

	// Check Body (simple check)
	if !bytes.Contains(rec.Body.Bytes(), []byte("Welcome")) {
		t.Errorf("Expected body to contain 'Welcome'")
	}
}

func TestCoreHandlers_Tickets(t *testing.T) {
	userID := "user-123"

	t.Run("CreateTicket", func(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()

		handlers := createTestCoreHandlers(t, db, nil)

		// Mock Insert: user_id, subject, priority
		mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO tickets`)).
			WithArgs(userID, "Issue", "low").
			WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "subject", "status", "priority", "created_at", "updated_at"}).
				AddRow("ticket-1", userID, "Issue", "open", "low", time.Now(), time.Now()))

		payload := map[string]string{
			"subject":  "Issue",
			"message":  "Help",
			"priority": "low",
		}
		body, _ := json.Marshal(payload)
		req := httptest.NewRequest(http.MethodPost, "/api/v1/support/tickets", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		ctx := context.WithValue(req.Context(), middleware.ContextUserID, userID)
		rec := httptest.NewRecorder()

		handlers.CreateTicket(rec, req.WithContext(ctx))

		if rec.Code != http.StatusCreated {
			t.Errorf("CreateTicket status = %d, want %d. Body: %s", rec.Code, http.StatusCreated, rec.Body.String())
		}
	})

	t.Run("ListTickets", func(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()

		handlers := createTestCoreHandlers(t, db, nil)

		// Mock Select
		mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, user_id, subject, status, priority, created_at, updated_at FROM tickets`)).
			WithArgs(userID).
			WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "subject", "status", "priority", "created_at", "updated_at"}).
				AddRow("ticket-1", userID, "Issue", "open", "low", time.Now(), time.Now()))

		req := httptest.NewRequest(http.MethodGet, "/api/v1/support/tickets", nil)
		ctx := context.WithValue(req.Context(), middleware.ContextUserID, userID)
		rec := httptest.NewRecorder()

		handlers.ListTickets(rec, req.WithContext(ctx))

		if rec.Code != http.StatusOK {
			t.Errorf("ListTickets status = %d, want %d. Body: %s", rec.Code, http.StatusOK, rec.Body.String())
		}
	})
}
