package handler

import (
	"baron-sso-backend/internal/domain"
	"baron-sso-backend/internal/service"
	"bytes"
	"context"
	"errors"
	"io"
	"log/slog"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/gofiber/fiber/v2"
	"github.com/stretchr/testify/require"
)

func TestWorksmobileHandlerRejectsNonHanmacTenant(t *testing.T) {
	h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
		overview: service.WorksmobileTenantOverview{
			Tenant: domain.Tenant{ID: "tenant-1", Slug: "other"},
		},
	})
	app := fiber.New()
	app.Get("/tenants/:tenantId/worksmobile", h.GetOverview)

	resp, err := app.Test(httptest.NewRequest("GET", "/tenants/tenant-1/worksmobile", nil))

	require.NoError(t, err)
	require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
}

func TestWorksmobileHandlerReturnsOverviewForHanmacTenant(t *testing.T) {
	h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
		overview: service.WorksmobileTenantOverview{
			Tenant: domain.Tenant{ID: "hanmac-id", Slug: "hanmac-family"},
			Config: service.WorksmobileConfigSummary{
				Enabled: true,
			},
		},
	})
	app := fiber.New()
	app.Get("/tenants/:tenantId/worksmobile", h.GetOverview)

	resp, err := app.Test(httptest.NewRequest("GET", "/tenants/hanmac-id/worksmobile", nil))

	require.NoError(t, err)
	require.Equal(t, fiber.StatusOK, resp.StatusCode)
}

func TestWorksmobileHandlerDownloadsInitialPasswordCSV(t *testing.T) {
	h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
		credentials: []service.WorksmobileInitialPasswordCredential{
			{
				Email:              "user@hanmaceng.co.kr",
				Name:               "홍길동",
				PrimaryLeafOrgName: "인재성장",
				InitialPassword:    "Aa1!Aa1!Aa1!Aa1!",
				Status:             "processed",
			},
		},
	})
	app := fiber.New()
	app.Get("/tenants/:tenantId/worksmobile/initial-passwords.csv", h.DownloadInitialPasswordsCSV)

	resp, err := app.Test(httptest.NewRequest("GET", "/tenants/hanmac-id/worksmobile/initial-passwords.csv", nil))
	require.NoError(t, err)
	require.Equal(t, fiber.StatusOK, resp.StatusCode)
	require.Contains(t, resp.Header.Get("Content-Disposition"), "worksmobile_initial_passwords.csv")
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)
	require.Contains(t, string(body), "email,name,primaryLeafOrgName,initialPassword,status,lastError")
	require.Contains(t, string(body), "user@hanmaceng.co.kr,홍길동,인재성장,Aa1!Aa1!Aa1!Aa1!,processed,")
}

func TestWorksmobileHandlerPassesInitialPasswordBatchID(t *testing.T) {
	fakeService := &fakeWorksmobileAdminService{
		credentials: []service.WorksmobileInitialPasswordCredential{
			{Email: "batch-user@hanmaceng.co.kr", InitialPassword: "BatchPass1!", Status: "pending"},
		},
	}
	h := NewWorksmobileHandler(fakeService)
	app := fiber.New()
	app.Get("/tenants/:tenantId/worksmobile/initial-passwords.csv", h.DownloadInitialPasswordsCSV)

	resp, err := app.Test(httptest.NewRequest("GET", "/tenants/hanmac-id/worksmobile/initial-passwords.csv?batchId=batch-1", nil))

	require.NoError(t, err)
	require.Equal(t, fiber.StatusOK, resp.StatusCode)
	require.Equal(t, "batch-1", fakeService.downloadCredentialBatchID)
}

func TestWorksmobileHandlerPassesSyncUserCredentialBatchID(t *testing.T) {
	fakeService := &fakeWorksmobileAdminService{}
	h := NewWorksmobileHandler(fakeService)
	app := fiber.New()
	app.Post("/tenants/:tenantId/worksmobile/users/:userId/sync", h.SyncUser)

	req := httptest.NewRequest("POST", "/tenants/hanmac-id/worksmobile/users/user-1/sync", strings.NewReader(`{"credentialBatchId":"batch-1","initialPassword":"InputPass1!"}`))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)

	require.NoError(t, err)
	require.Equal(t, fiber.StatusAccepted, resp.StatusCode)
	require.Equal(t, "batch-1", fakeService.syncUserCredentialBatchID)
	require.Equal(t, "InputPass1!", fakeService.syncUserInitialPassword)
}

func TestWorksmobileHandlerPassesPasswordResetCredentialBatchID(t *testing.T) {
	fakeService := &fakeWorksmobileAdminService{}
	h := NewWorksmobileHandler(fakeService)
	app := fiber.New()
	app.Post("/tenants/:tenantId/worksmobile/users/:userId/password/reset", h.ResetUserPassword)

	req := httptest.NewRequest("POST", "/tenants/hanmac-id/worksmobile/users/user-1/password/reset", strings.NewReader(`{"credentialBatchId":"batch-1"}`))
	req.Header.Set("Content-Type", "application/json")
	resp, err := app.Test(req)

	require.NoError(t, err)
	require.Equal(t, fiber.StatusAccepted, resp.StatusCode)
	require.Equal(t, "batch-1", fakeService.resetPasswordCredentialBatchID)
}

func TestWorksmobileHandlerReturnsCredentialBatchHistory(t *testing.T) {
	h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
		credentialBatches: []service.WorksmobileCredentialBatch{
			{BatchID: "batch-1", UserCount: 2, HasPasswords: true},
		},
	})
	app := fiber.New()
	app.Get("/tenants/:tenantId/worksmobile/credential-batches", h.ListCredentialBatches)

	resp, err := app.Test(httptest.NewRequest("GET", "/tenants/hanmac-id/worksmobile/credential-batches", nil))

	require.NoError(t, err)
	require.Equal(t, fiber.StatusOK, resp.StatusCode)
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)
	require.Contains(t, string(body), `"batchId":"batch-1"`)
	require.Contains(t, string(body), `"userCount":2`)
}

func TestWorksmobileHandlerDeletesCredentialBatchPasswords(t *testing.T) {
	fakeService := &fakeWorksmobileAdminService{}
	h := NewWorksmobileHandler(fakeService)
	app := fiber.New()
	app.Delete("/tenants/:tenantId/worksmobile/credential-batches/:batchId/passwords", h.DeleteCredentialBatchPasswords)

	resp, err := app.Test(httptest.NewRequest("DELETE", "/tenants/hanmac-id/worksmobile/credential-batches/batch-1/passwords", nil))

	require.NoError(t, err)
	require.Equal(t, fiber.StatusOK, resp.StatusCode)
	require.Equal(t, "batch-1", fakeService.deletedCredentialBatchID)
}

func TestWorksmobileHandlerDeletesPendingJobs(t *testing.T) {
	fakeService := &fakeWorksmobileAdminService{
		pendingJobsDeleteResult: service.WorksmobilePendingJobDeleteResult{DeletedCount: 3},
	}
	h := NewWorksmobileHandler(fakeService)
	app := fiber.New()
	app.Delete("/tenants/:tenantId/worksmobile/jobs/pending", h.DeletePendingJobs)

	resp, err := app.Test(httptest.NewRequest("DELETE", "/tenants/hanmac-id/worksmobile/jobs/pending", nil))

	require.NoError(t, err)
	require.Equal(t, fiber.StatusOK, resp.StatusCode)
	require.Equal(t, "hanmac-id", fakeService.deletedPendingJobsTenantID)
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)
	require.Contains(t, string(body), `"deletedCount":3`)
}

func TestWorksmobileHandlerLogsActionFailures(t *testing.T) {
	var logs bytes.Buffer
	previous := slog.Default()
	slog.SetDefault(slog.New(slog.NewJSONHandler(&logs, nil)))
	t.Cleanup(func() {
		slog.SetDefault(previous)
	})

	h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
		syncUserErr: errors.New("works user sync failed"),
	})
	app := fiber.New()
	app.Post("/tenants/:tenantId/worksmobile/users/:userId/sync", h.SyncUser)

	resp, err := app.Test(httptest.NewRequest("POST", "/tenants/hanmac-id/worksmobile/users/user-1/sync", nil))

	require.NoError(t, err)
	require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
	require.Contains(t, logs.String(), "worksmobile admin operation failed")
	require.Contains(t, logs.String(), "sync_user")
	require.Contains(t, logs.String(), "works user sync failed")
}

type fakeWorksmobileAdminService struct {
	overview                       service.WorksmobileTenantOverview
	credentials                    []service.WorksmobileInitialPasswordCredential
	syncUserErr                    error
	syncUserCredentialBatchID      string
	syncUserInitialPassword        string
	resetPasswordCredentialBatchID string
	downloadCredentialBatchID      string
	deletedCredentialBatchID       string
	deletedPendingJobsTenantID     string
	pendingJobsDeleteResult        service.WorksmobilePendingJobDeleteResult
	credentialBatches              []service.WorksmobileCredentialBatch
}

func (f *fakeWorksmobileAdminService) GetTenantOverview(ctx context.Context, tenantID string) (service.WorksmobileTenantOverview, error) {
	return f.overview, nil
}

func (f *fakeWorksmobileAdminService) GetComparison(ctx context.Context, tenantID string, includeMatched bool) (service.WorksmobileComparison, error) {
	return service.WorksmobileComparison{}, nil
}

func (f *fakeWorksmobileAdminService) EnqueueBackfillDryRun(ctx context.Context, tenantID string) (service.WorksmobileBackfillDryRun, error) {
	return service.WorksmobileBackfillDryRun{}, nil
}

func (f *fakeWorksmobileAdminService) EnqueueOrgUnitSync(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error) {
	return &domain.WorksmobileOutbox{ID: "job-orgunit", ResourceID: orgUnitID}, nil
}

func (f *fakeWorksmobileAdminService) EnqueueOrgUnitDelete(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error) {
	return &domain.WorksmobileOutbox{ID: "job-orgunit-delete", ResourceID: orgUnitID, Action: domain.WorksmobileActionDelete}, nil
}

func (f *fakeWorksmobileAdminService) EnqueueUserSync(ctx context.Context, tenantID, userID, credentialBatchID, initialPassword string) (*domain.WorksmobileOutbox, error) {
	f.syncUserCredentialBatchID = credentialBatchID
	f.syncUserInitialPassword = initialPassword
	if f.syncUserErr != nil {
		return nil, f.syncUserErr
	}
	return &domain.WorksmobileOutbox{ID: "job-user", ResourceID: userID}, nil
}

func (f *fakeWorksmobileAdminService) EnqueueUserPasswordReset(ctx context.Context, tenantID, userID, credentialBatchID string) (*domain.WorksmobileOutbox, error) {
	f.resetPasswordCredentialBatchID = credentialBatchID
	return &domain.WorksmobileOutbox{ID: "job-user-password-reset", ResourceID: userID}, nil
}

func (f *fakeWorksmobileAdminService) RetryJob(ctx context.Context, tenantID, jobID string) (*domain.WorksmobileOutbox, error) {
	return &domain.WorksmobileOutbox{ID: jobID}, nil
}

func (f *fakeWorksmobileAdminService) ListInitialPasswordCredentials(ctx context.Context, tenantID, credentialBatchID string) ([]service.WorksmobileInitialPasswordCredential, error) {
	f.downloadCredentialBatchID = credentialBatchID
	return f.credentials, nil
}

func (f *fakeWorksmobileAdminService) ListCredentialBatches(ctx context.Context, tenantID string) ([]service.WorksmobileCredentialBatch, error) {
	return f.credentialBatches, nil
}

func (f *fakeWorksmobileAdminService) DeleteCredentialBatchPasswords(ctx context.Context, tenantID, credentialBatchID string) (service.WorksmobileCredentialBatch, error) {
	f.deletedCredentialBatchID = credentialBatchID
	return service.WorksmobileCredentialBatch{BatchID: credentialBatchID}, nil
}

func (f *fakeWorksmobileAdminService) DeletePendingJobs(ctx context.Context, tenantID string) (service.WorksmobilePendingJobDeleteResult, error) {
	f.deletedPendingJobsTenantID = tenantID
	return f.pendingJobsDeleteResult, nil
}
