package handlers

import (
	"bytes"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/gofiber/fiber/v2"

	filemanager "wucher/internal/domain/file_manager"
	mastersettings "wucher/internal/domain/master_settings"
	"wucher/internal/shared/pkg/uuidv7"
)

func newBrandingTestApp(h *BrandingHandler) *fiber.App {
	app := fiber.New()
	app.Get("/api/v1/branding/logo", h.GetLogo)
	app.Get("/api/v1/branding/login-cover", h.GetLoginCover)
	app.Get("/api/v1/branding/login-content", h.GetLoginContent)
	app.Get("/api/v1/public/branding/email-logo", h.GetEmailLogo)
	return app
}

func doBrandingRequest(t *testing.T, app *fiber.App, req *http.Request) (*http.Response, []byte) {
	t.Helper()
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("read body: %v", err)
	}
	return resp, body
}

func TestBrandingHandlerGetLogoManagedSuccess(t *testing.T) {
	logoBytes := []byte("PNGDATA")
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/png")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write(logoBytes)
	}))
	defer upstream.Close()

	logoFileID := uuidv7.MustBytes()
	msSvc := &masterSettingsServiceMock{
		listRows: []mastersettings.MasterSettings{{
			ID:         uuidv7.MustBytes(),
			LogoFileID: logoFileID,
			UpdatedAt:  time.Date(2026, time.April, 1, 0, 0, 0, 0, time.UTC),
		}},
	}
	fileRepo := &masterSettingsFileRepoMock{
		getByIDRow: &filemanager.File{
			ID:            logoFileID,
			ObjectKey:     "branding/company-logo.png",
			Status:        filemanager.FileStatusReady,
			MimeType:      "image/png",
			ETag:          "etag-1",
			ObjectVersion: "v1",
			UpdatedAt:     time.Date(2026, time.April, 2, 0, 0, 0, 0, time.UTC),
		},
	}
	storage := &masterSettingsStorageMock{presignURL: upstream.URL}

	h := NewBrandingHandler(msSvc, newMasterSettingsFileService(fileRepo), storage)
	app := newBrandingTestApp(h)

	req, _ := http.NewRequest(http.MethodGet, "/api/v1/branding/logo", nil)
	resp, body := doBrandingRequest(t, app, req)

	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if !bytes.Equal(body, logoBytes) {
		t.Fatalf("unexpected logo body: %q", string(body))
	}
	if got := resp.Header.Get("Content-Type"); !strings.Contains(got, "image/png") {
		t.Fatalf("expected image/png, got %q", got)
	}
	if got := resp.Header.Get("Cache-Control"); got != brandingLogoManagedCacheControl {
		t.Fatalf("expected managed cache-control, got %q", got)
	}
	if got := strings.TrimSpace(resp.Header.Get("ETag")); got == "" {
		t.Fatalf("expected ETag header")
	}
	if got := resp.Header.Get("Last-Modified"); strings.TrimSpace(got) == "" {
		t.Fatalf("expected Last-Modified header")
	}
	if got := resp.Header.Get("Vary"); !strings.Contains(got, "Origin") {
		t.Fatalf("expected Origin in Vary, got %q", got)
	}
	if storage.lastKey != "branding/company-logo.png" {
		t.Fatalf("expected object key passed to presign, got %q", storage.lastKey)
	}
}

func TestBrandingHandlerGetLogoNotModifiedByETag(t *testing.T) {
	logoBytes := []byte("PNGDATA")
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/png")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write(logoBytes)
	}))
	defer upstream.Close()

	logoFileID := uuidv7.MustBytes()
	msSvc := &masterSettingsServiceMock{
		listRows: []mastersettings.MasterSettings{{
			ID:         uuidv7.MustBytes(),
			LogoFileID: logoFileID,
			UpdatedAt:  time.Date(2026, time.April, 1, 0, 0, 0, 0, time.UTC),
		}},
	}
	fileRepo := &masterSettingsFileRepoMock{
		getByIDRow: &filemanager.File{
			ID:            logoFileID,
			ObjectKey:     "branding/company-logo.png",
			Status:        filemanager.FileStatusReady,
			MimeType:      "image/png",
			ETag:          "etag-1",
			ObjectVersion: "v1",
			UpdatedAt:     time.Date(2026, time.April, 2, 0, 0, 0, 0, time.UTC),
		},
	}
	storage := &masterSettingsStorageMock{presignURL: upstream.URL}

	h := NewBrandingHandler(msSvc, newMasterSettingsFileService(fileRepo), storage)
	app := newBrandingTestApp(h)

	req1, _ := http.NewRequest(http.MethodGet, "/api/v1/branding/logo", nil)
	resp1, _ := doBrandingRequest(t, app, req1)
	if resp1.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp1.StatusCode)
	}
	etag := resp1.Header.Get("ETag")
	if strings.TrimSpace(etag) == "" {
		t.Fatalf("expected etag header")
	}

	req2, _ := http.NewRequest(http.MethodGet, "/api/v1/branding/logo", nil)
	req2.Header.Set("If-None-Match", etag)
	resp2, body2 := doBrandingRequest(t, app, req2)
	if resp2.StatusCode != http.StatusNotModified {
		t.Fatalf("expected 304, got %d", resp2.StatusCode)
	}
	if len(body2) != 0 {
		t.Fatalf("expected empty body for 304, got %d bytes", len(body2))
	}
}

func TestBrandingHandlerGetLogoFallbackWhenNoLogoConfigured(t *testing.T) {
	msSvc := &masterSettingsServiceMock{listRows: []mastersettings.MasterSettings{}}
	h := NewBrandingHandler(msSvc, nil, nil)
	app := newBrandingTestApp(h)

	req, _ := http.NewRequest(http.MethodGet, "/api/v1/branding/logo", nil)
	resp, body := doBrandingRequest(t, app, req)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if got := resp.Header.Get("Cache-Control"); got != brandingFallbackCache {
		t.Fatalf("expected fallback cache-control, got %q", got)
	}
	if got := resp.Header.Get("Content-Type"); !strings.Contains(got, "image/svg+xml") {
		t.Fatalf("expected svg content-type, got %q", got)
	}
	if !strings.Contains(string(body), "COMPANY") {
		t.Fatalf("expected default logo body, got %q", string(body))
	}
}

func TestBrandingHandlerGetEmailLogoAlias(t *testing.T) {
	msSvc := &masterSettingsServiceMock{listRows: []mastersettings.MasterSettings{}}
	h := NewBrandingHandler(msSvc, nil, nil)
	app := newBrandingTestApp(h)

	req, _ := http.NewRequest(http.MethodGet, "/api/v1/public/branding/email-logo", nil)
	resp, body := doBrandingRequest(t, app, req)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if got := resp.Header.Get("Content-Type"); !strings.Contains(got, "image/png") {
		t.Fatalf("expected png content-type, got %q", got)
	}
	if len(body) == 0 {
		t.Fatalf("expected default logo body")
	}
	if got := resp.Header.Get("Cross-Origin-Resource-Policy"); got != "cross-origin" {
		t.Fatalf("expected cross-origin CORP header, got %q", got)
	}
	if got := resp.Header.Get("Cross-Origin-Embedder-Policy"); got != "" {
		t.Fatalf("expected no COEP header, got %q", got)
	}
	if got := resp.Header.Get("Cross-Origin-Opener-Policy"); got != "" {
		t.Fatalf("expected no COOP header, got %q", got)
	}
}

func TestBrandingHandlerGetEmailLogoPrefersThumbnail(t *testing.T) {
	logoBytes := []byte("PNGDATA")
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/png")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write(logoBytes)
	}))
	defer upstream.Close()

	logoFileID := uuidv7.MustBytes()
	thumbMime := "image/png"
	thumbKey := "branding/company-logo-thumb.png"
	msSvc := &masterSettingsServiceMock{
		listRows: []mastersettings.MasterSettings{{
			ID:         uuidv7.MustBytes(),
			LogoFileID: logoFileID,
			UpdatedAt:  time.Date(2026, time.April, 1, 0, 0, 0, 0, time.UTC),
		}},
	}
	fileRepo := &masterSettingsFileRepoMock{
		getByIDRow: &filemanager.File{
			ID:                 logoFileID,
			ObjectKey:          "branding/company-logo.svg",
			Status:             filemanager.FileStatusReady,
			MimeType:           "image/svg+xml",
			ThumbnailMimeType:  &thumbMime,
			ThumbnailObjectKey: &thumbKey,
			UpdatedAt:          time.Date(2026, time.April, 2, 0, 0, 0, 0, time.UTC),
		},
	}
	storage := &masterSettingsStorageMock{presignURL: upstream.URL}

	h := NewBrandingHandler(msSvc, newMasterSettingsFileService(fileRepo), storage)
	app := newBrandingTestApp(h)

	req, _ := http.NewRequest(http.MethodGet, "/api/v1/public/branding/email-logo", nil)
	resp, body := doBrandingRequest(t, app, req)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if !bytes.Equal(body, logoBytes) {
		t.Fatalf("unexpected email logo body: %q", string(body))
	}
	if got := resp.Header.Get("Content-Type"); !strings.Contains(got, "image/png") {
		t.Fatalf("expected image/png, got %q", got)
	}
	if storage.lastKey != thumbKey {
		t.Fatalf("expected thumbnail object key, got %q", storage.lastKey)
	}
}

func TestBrandingHandlerGetEmailLogoFallsBackFromSVG(t *testing.T) {
	logoFileID := uuidv7.MustBytes()
	msSvc := &masterSettingsServiceMock{
		listRows: []mastersettings.MasterSettings{{
			ID:         uuidv7.MustBytes(),
			LogoFileID: logoFileID,
			UpdatedAt:  time.Date(2026, time.April, 1, 0, 0, 0, 0, time.UTC),
		}},
	}
	fileRepo := &masterSettingsFileRepoMock{
		getByIDRow: &filemanager.File{
			ID:        logoFileID,
			ObjectKey: "branding/company-logo.svg",
			Status:    filemanager.FileStatusReady,
			MimeType:  "image/svg+xml",
			UpdatedAt: time.Date(2026, time.April, 2, 0, 0, 0, 0, time.UTC),
		},
	}
	h := NewBrandingHandler(msSvc, newMasterSettingsFileService(fileRepo), &masterSettingsStorageMock{})
	app := newBrandingTestApp(h)

	req, _ := http.NewRequest(http.MethodGet, "/api/v1/public/branding/email-logo", nil)
	resp, body := doBrandingRequest(t, app, req)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if got := resp.Header.Get("Content-Type"); !strings.Contains(got, "image/png") {
		t.Fatalf("expected image/png fallback, got %q", got)
	}
	if len(body) == 0 {
		t.Fatalf("expected png fallback body")
	}
}

func TestBrandingHandlerGetLogoFallbackWhenPresignFails(t *testing.T) {
	logoFileID := uuidv7.MustBytes()
	msSvc := &masterSettingsServiceMock{
		listRows: []mastersettings.MasterSettings{{
			ID:         uuidv7.MustBytes(),
			LogoFileID: logoFileID,
			UpdatedAt:  time.Date(2026, time.April, 1, 0, 0, 0, 0, time.UTC),
		}},
	}
	fileRepo := &masterSettingsFileRepoMock{
		getByIDRow: &filemanager.File{
			ID:        logoFileID,
			ObjectKey: "branding/company-logo.png",
			Status:    filemanager.FileStatusReady,
			MimeType:  "image/png",
		},
	}
	storage := &masterSettingsStorageMock{presignErr: errors.New("presign failed")}
	h := NewBrandingHandler(msSvc, newMasterSettingsFileService(fileRepo), storage)
	app := newBrandingTestApp(h)

	req, _ := http.NewRequest(http.MethodGet, "/api/v1/branding/logo", nil)
	resp, body := doBrandingRequest(t, app, req)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if got := resp.Header.Get("Cache-Control"); got != brandingFallbackCache {
		t.Fatalf("expected fallback cache-control, got %q", got)
	}
	if !strings.Contains(string(body), "COMPANY") {
		t.Fatalf("expected default logo body, got %q", string(body))
	}
}

func TestBrandingHandlerGetLoginCoverManagedSuccess(t *testing.T) {
	coverBytes := []byte("COVERDATA")
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/jpeg")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write(coverBytes)
	}))
	defer upstream.Close()

	coverFileID := uuidv7.MustBytes()
	msSvc := &masterSettingsServiceMock{
		listRows: []mastersettings.MasterSettings{{
			ID:          uuidv7.MustBytes(),
			CoverFileID: coverFileID,
			UpdatedAt:   time.Date(2026, time.April, 1, 0, 0, 0, 0, time.UTC),
		}},
	}
	fileRepo := &masterSettingsFileRepoMock{
		getByIDRow: &filemanager.File{
			ID:            coverFileID,
			ObjectKey:     "branding/login-cover.jpg",
			Status:        filemanager.FileStatusReady,
			MimeType:      "image/jpeg",
			ETag:          "etag-cover-1",
			ObjectVersion: "v1",
			UpdatedAt:     time.Date(2026, time.April, 2, 0, 0, 0, 0, time.UTC),
		},
	}
	storage := &masterSettingsStorageMock{presignURL: upstream.URL}
	h := NewBrandingHandler(msSvc, newMasterSettingsFileService(fileRepo), storage)
	app := newBrandingTestApp(h)

	req, _ := http.NewRequest(http.MethodGet, "/api/v1/branding/login-cover", nil)
	resp, body := doBrandingRequest(t, app, req)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if !bytes.Equal(body, coverBytes) {
		t.Fatalf("unexpected cover body: %q", string(body))
	}
	if got := resp.Header.Get("Cache-Control"); got != brandingCoverManagedCacheControl {
		t.Fatalf("expected managed cover cache-control, got %q", got)
	}
	if got := resp.Header.Get("Cross-Origin-Resource-Policy"); got != brandingCORPMode {
		t.Fatalf("expected CORP header %q, got %q", brandingCORPMode, got)
	}
}

func TestBrandingHandlerGetLoginCoverNotModifiedByLastModified(t *testing.T) {
	coverBytes := []byte("COVERDATA")
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/jpeg")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write(coverBytes)
	}))
	defer upstream.Close()

	coverFileID := uuidv7.MustBytes()
	lastUpdated := time.Date(2026, time.April, 2, 10, 0, 0, 0, time.UTC)
	msSvc := &masterSettingsServiceMock{
		listRows: []mastersettings.MasterSettings{{
			ID:          uuidv7.MustBytes(),
			CoverFileID: coverFileID,
			UpdatedAt:   lastUpdated,
		}},
	}
	fileRepo := &masterSettingsFileRepoMock{
		getByIDRow: &filemanager.File{
			ID:        coverFileID,
			ObjectKey: "branding/login-cover.jpg",
			Status:    filemanager.FileStatusReady,
			MimeType:  "image/jpeg",
			UpdatedAt: lastUpdated,
		},
	}
	storage := &masterSettingsStorageMock{presignURL: upstream.URL}
	h := NewBrandingHandler(msSvc, newMasterSettingsFileService(fileRepo), storage)
	app := newBrandingTestApp(h)

	req, _ := http.NewRequest(http.MethodGet, "/api/v1/branding/login-cover", nil)
	req.Header.Set("If-Modified-Since", lastUpdated.Format(http.TimeFormat))
	resp, body := doBrandingRequest(t, app, req)
	if resp.StatusCode != http.StatusNotModified {
		t.Fatalf("expected 304, got %d", resp.StatusCode)
	}
	if len(body) != 0 {
		t.Fatalf("expected empty body, got %d bytes", len(body))
	}
}

func TestBrandingHandlerGetLoginContentSuccessAndNotModified(t *testing.T) {
	updatedAt := time.Date(2026, time.April, 16, 10, 30, 0, 0, time.UTC)
	msSvc := &masterSettingsServiceMock{
		listRows: []mastersettings.MasterSettings{{
			ID:        uuidv7.MustBytes(),
			Title:     "Welcome back",
			Subtitle:  "Sign in to continue",
			UpdatedAt: updatedAt,
		}},
	}
	h := NewBrandingHandler(msSvc, nil, nil)
	app := newBrandingTestApp(h)

	req1, _ := http.NewRequest(http.MethodGet, "/api/v1/branding/login-content", nil)
	resp1, body1 := doBrandingRequest(t, app, req1)
	if resp1.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp1.StatusCode)
	}
	if !strings.Contains(string(body1), `"type":"login_branding"`) {
		t.Fatalf("unexpected body: %s", string(body1))
	}
	if !strings.Contains(string(body1), `"version":"2026-04-16T10:30:00Z"`) {
		t.Fatalf("expected version in body: %s", string(body1))
	}
	etag := strings.TrimSpace(resp1.Header.Get("ETag"))
	if etag == "" {
		t.Fatalf("expected etag header")
	}

	req2, _ := http.NewRequest(http.MethodGet, "/api/v1/branding/login-content", nil)
	req2.Header.Set("If-None-Match", etag)
	resp2, body2 := doBrandingRequest(t, app, req2)
	if resp2.StatusCode != http.StatusNotModified {
		t.Fatalf("expected 304, got %d", resp2.StatusCode)
	}
	if len(body2) != 0 {
		t.Fatalf("expected empty body for 304, got %d bytes", len(body2))
	}
}

func TestBrandingHandlerGetLoginContentFallbackDefaults(t *testing.T) {
	msSvc := &masterSettingsServiceMock{
		listRows: []mastersettings.MasterSettings{{
			ID:        uuidv7.MustBytes(),
			Title:     "",
			Subtitle:  "",
			UpdatedAt: time.Date(2026, time.April, 16, 10, 30, 0, 0, time.UTC),
		}},
	}
	h := NewBrandingHandler(msSvc, nil, nil)
	app := newBrandingTestApp(h)

	req, _ := http.NewRequest(http.MethodGet, "/api/v1/branding/login-content", nil)
	resp, body := doBrandingRequest(t, app, req)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if !strings.Contains(string(body), `"title":"Welcome back"`) {
		t.Fatalf("expected default title, got %s", string(body))
	}
	if !strings.Contains(string(body), `"subtitle":"Sign in to continue"`) {
		t.Fatalf("expected default subtitle, got %s", string(body))
	}
	if got := resp.Header.Get("Cache-Control"); got != brandingContentCacheControl {
		t.Fatalf("expected content cache-control %q, got %q", brandingContentCacheControl, got)
	}
}
