package pmg

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestNewClientAuthenticatesWithJSONPayload(t *testing.T) {
	t.Parallel()

	var authCalls int
	var versionCalls int

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api2/json/access/ticket":
			authCalls++

			if r.Method != http.MethodPost {
				t.Fatalf("expected POST for auth, got %s", r.Method)
			}

			if ct := r.Header.Get("Content-Type"); ct != "application/json" {
				t.Fatalf("expected JSON auth content-type, got %s", ct)
			}

			var payload map[string]string
			if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
				t.Fatalf("failed decoding auth payload: %v", err)
			}

			if payload["username"] != "api@pmg" {
				t.Fatalf("expected username api@pmg, got %s", payload["username"])
			}

			if payload["password"] != "secret" {
				t.Fatalf("expected password secret, got %s", payload["password"])
			}

			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{"ticket":"ticket123","CSRFPreventionToken":"csrf123"}}`)

		case "/api2/json/version":
			versionCalls++

			cookie := r.Header.Get("Cookie")
			if !strings.Contains(cookie, "PMGAuthCookie=ticket123") {
				t.Fatalf("expected auth cookie, got %s", cookie)
			}

			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{"version":"8.0.0","release":"1"}}`)

		default:
			t.Fatalf("unexpected request path: %s", r.URL.Path)
		}
	}))
	defer server.Close()

	client, err := NewClient(ClientConfig{
		Host:      server.URL,
		User:      "api@pmg",
		Password:  "secret",
		VerifySSL: false,
	})
	if err != nil {
		t.Fatalf("unexpected error creating client: %v", err)
	}

	info, err := client.GetVersion(context.Background())
	if err != nil {
		t.Fatalf("get version failed: %v", err)
	}

	if info == nil || info.Version != "8.0.0" {
		t.Fatalf("expected version 8.0.0, got %+v", info)
	}

	if authCalls != 1 {
		t.Fatalf("expected one auth call, got %d", authCalls)
	}

	if versionCalls != 1 {
		t.Fatalf("expected one version call, got %d", versionCalls)
	}
}

func TestClientAuthenticateFallsBackToForm(t *testing.T) {
	t.Parallel()

	var authCalls int
	var jsonAuthReceived bool
	var formAuthReceived bool

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api2/json/access/ticket":
			authCalls++

			switch authCalls {
			case 1:
				if ct := r.Header.Get("Content-Type"); ct != "application/json" {
					t.Fatalf("first auth call should use JSON content-type, got %s", ct)
				}
				jsonAuthReceived = true

				w.WriteHeader(http.StatusUnsupportedMediaType)
				fmt.Fprint(w, "use form encoding")

			case 2:
				if ct := r.Header.Get("Content-Type"); ct != "application/x-www-form-urlencoded" {
					t.Fatalf("fallback auth should use form encoding, got %s", ct)
				}

				body, err := io.ReadAll(r.Body)
				if err != nil {
					t.Fatalf("failed reading form body: %v", err)
				}

				formValues := string(body)
				if !strings.Contains(formValues, "username=api%40pmg") {
					t.Fatalf("expected username in form body, got %s", formValues)
				}

				formAuthReceived = true

				w.Header().Set("Content-Type", "application/json")
				fmt.Fprint(w, `{"data":{"ticket":"ticket456","CSRFPreventionToken":"csrf456"}}`)

			default:
				t.Fatalf("unexpected auth attempt %d", authCalls)
			}

		case "/api2/json/version":
			if !strings.Contains(r.Header.Get("Cookie"), "PMGAuthCookie=ticket456") {
				t.Fatalf("expected fallback auth cookie, got %s", r.Header.Get("Cookie"))
			}
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{"version":"9.0.0"}}`)

		default:
			t.Fatalf("unexpected request path: %s", r.URL.Path)
		}
	}))
	defer server.Close()

	client, err := NewClient(ClientConfig{
		Host:      server.URL,
		User:      "api@pmg",
		Password:  "secret",
		VerifySSL: false,
	})
	if err != nil {
		t.Fatalf("unexpected error creating client with fallback: %v", err)
	}

	if _, err := client.GetVersion(context.Background()); err != nil {
		t.Fatalf("get version after fallback failed: %v", err)
	}

	if authCalls != 2 {
		t.Fatalf("expected two auth attempts, got %d", authCalls)
	}

	if !jsonAuthReceived {
		t.Fatal("expected JSON auth request to be received")
	}

	if !formAuthReceived {
		t.Fatal("expected form-based auth fallback to be received")
	}
}

func TestClientUsesTokenAuthorizationHeader(t *testing.T) {
	t.Parallel()

	var authHeader string

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api2/json/statistics/mail":
			if r.Header.Get("Content-Type") != "" {
				t.Fatalf("expected no explicit content-type on GET, got %s", r.Header.Get("Content-Type"))
			}

			if r.Method != http.MethodGet {
				t.Fatalf("unexpected method %s", r.Method)
			}

			authHeader = r.Header.Get("Authorization")
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{"count":42}}`)

		case "/api2/json/access/ticket":
			t.Fatalf("token-based client should not request tickets")

		default:
			t.Fatalf("unexpected request path: %s", r.URL.Path)
		}
	}))
	defer server.Close()

	client, err := NewClient(ClientConfig{
		Host:       server.URL,
		User:       "apitest@pmg",
		TokenName:  "apitoken",
		TokenValue: "secret",
		VerifySSL:  false,
	})
	if err != nil {
		t.Fatalf("unexpected error creating token client: %v", err)
	}

	stats, err := client.GetMailStatistics(context.Background(), "")
	if err != nil {
		t.Fatalf("get mail statistics failed: %v", err)
	}

	if stats == nil || stats.Count.Float64() != 42 {
		t.Fatalf("expected statistics count 42, got %+v", stats)
	}

	expected := "PMGAPIToken=apitest@pmg!apitoken:secret"
	if authHeader != expected {
		t.Fatalf("expected authorization header %q, got %q", expected, authHeader)
	}
}

func TestListBackups(t *testing.T) {
	t.Parallel()

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api2/json/access/ticket":
			if r.Method != http.MethodPost {
				t.Fatalf("expected POST for auth, got %s", r.Method)
			}
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{"ticket":"ticket789","CSRFPreventionToken":"csrf789"}}`)
		case "/api2/json/nodes/node1/backup":
			if r.Method != http.MethodGet {
				t.Fatalf("expected GET for backup listing, got %s", r.Method)
			}
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":[{"filename":"pmg-backup_2024-01-01.tgz","size":123456,"timestamp":1704096000}]}`)
		default:
			t.Fatalf("unexpected request path: %s", r.URL.Path)
		}
	}))
	defer server.Close()

	client, err := NewClient(ClientConfig{
		Host:      server.URL,
		User:      "api@pmg",
		Password:  "secret",
		VerifySSL: false,
	})
	if err != nil {
		t.Fatalf("unexpected error creating client: %v", err)
	}

	backups, err := client.ListBackups(context.Background(), "node1")
	if err != nil {
		t.Fatalf("ListBackups failed: %v", err)
	}
	if len(backups) != 1 {
		t.Fatalf("expected 1 backup, got %d", len(backups))
	}
	backup := backups[0]
	if backup.Filename != "pmg-backup_2024-01-01.tgz" {
		t.Fatalf("unexpected filename: %s", backup.Filename)
	}
	if backup.Size.Int64() != 123456 {
		t.Fatalf("unexpected size: %d", backup.Size.Int64())
	}
	if backup.Timestamp.Int64() != 1704096000 {
		t.Fatalf("unexpected timestamp: %d", backup.Timestamp.Int64())
	}
}

func TestMailEndpointsHandleNullAndStringValues(t *testing.T) {
	t.Parallel()

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api2/json/access/ticket":
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{"ticket":"ticket","CSRFPreventionToken":"csrf"}}`)
		case "/api2/json/version":
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{"version":"9.0","release":"1"}}`)
		case "/api2/json/statistics/mail":
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{
				"count":null,
				"count_in":"25",
				"count_out":"5",
				"spamcount_in":"7",
				"spamcount_out":null,
				"viruscount_in":"0",
				"viruscount_out":"0",
				"bounces_in":null,
				"bounces_out":"",
				"bytes_in":"1024",
				"bytes_out":"2048",
				"glcount":"2",
				"junk_in":"",
				"rbl_rejects":"1",
				"pregreet_rejects":null,
				"avptime":"0.75"
			}}`)
		case "/api2/json/statistics/mailcount":
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":[{
				"index":"0",
				"time":"1713724800",
				"count":"12",
				"count_in":"8",
				"count_out":"4",
				"spamcount_in":null,
				"spamcount_out":"1",
				"viruscount_in":"",
				"viruscount_out":"0",
				"bounces_in":"1",
				"bounces_out":"0",
				"rbl_rejects":"2",
				"pregreet_rejects":"",
				"glcount":"3"
			}]}`)
		case "/api2/json/quarantine/spamstatus":
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{"count":null,"avgbytes":"512","mbytes":"0.5"}}`)
		case "/api2/json/quarantine/virusstatus":
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{"count":"4","avgbytes":"256","mbytes":"0.25"}}`)
		case "/api2/json/nodes/mail/postfix/queue":
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"data":{"active":"3","deferred":null,"hold":"1","incoming":"2","oldest_age":"600"}}`)
		default:
			t.Fatalf("unexpected path: %s", r.URL.Path)
		}
	}))
	defer server.Close()

	client, err := NewClient(ClientConfig{
		Host:      server.URL,
		User:      "api@pmg",
		Password:  "secret",
		VerifySSL: false,
	})
	if err != nil {
		t.Fatalf("unexpected error creating client: %v", err)
	}

	ctx := context.Background()

	stats, err := client.GetMailStatistics(ctx, "")
	if err != nil {
		t.Fatalf("GetMailStatistics failed: %v", err)
	}
	if stats == nil {
		t.Fatal("expected statistics data")
	}
	if stats.Count.Float64() != 0 {
		t.Fatalf("expected count to default to 0, got %v", stats.Count.Float64())
	}
	if stats.CountIn.Float64() != 25 {
		t.Fatalf("expected CountIn 25, got %v", stats.CountIn.Float64())
	}
	if stats.BytesIn.Float64() != 1024 {
		t.Fatalf("expected BytesIn 1024, got %v", stats.BytesIn.Float64())
	}
	if stats.AvgProcessSec.Float64() != 0.75 {
		t.Fatalf("expected AvgProcessSec 0.75, got %v", stats.AvgProcessSec.Float64())
	}

	counts, err := client.GetMailCount(ctx, 12)
	if err != nil {
		t.Fatalf("GetMailCount failed: %v", err)
	}
	if len(counts) != 1 {
		t.Fatalf("expected 1 mail count entry, got %d", len(counts))
	}
	entry := counts[0]
	if entry.Time.Int64() != 1713724800 {
		t.Fatalf("expected unix time 1713724800, got %d", entry.Time.Int64())
	}
	if entry.Count.Float64() != 12 {
		t.Fatalf("expected count 12, got %v", entry.Count.Float64())
	}
	if entry.GreylistCount.Float64() != 3 {
		t.Fatalf("expected greylist 3, got %v", entry.GreylistCount.Float64())
	}

	quarantine, err := client.GetQuarantineStatus(ctx, "spam")
	if err != nil {
		t.Fatalf("GetQuarantineStatus failed: %v", err)
	}
	if quarantine.Count.Int64() != 0 {
		t.Fatalf("expected spam quarantine count default to 0, got %d", quarantine.Count.Int64())
	}

	queue, err := client.GetQueueStatus(ctx, "mail")
	if err != nil {
		t.Fatalf("GetQueueStatus failed: %v", err)
	}
	if queue.Active.Int() != 3 || queue.Deferred.Int() != 0 || queue.Hold.Int() != 1 || queue.Incoming.Int() != 2 {
		t.Fatalf("unexpected queue values: %+v", queue)
	}
	if queue.OldestAge.Int64() != 600 {
		t.Fatalf("expected oldest age 600, got %d", queue.OldestAge.Int64())
	}
}
