// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package wallabag

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

	"miniflux.app/v2/internal/config"
)

func TestCreateEntry(t *testing.T) {
	configureIntegrationAllowPrivateNetworksOption(t)

	entryURL := "https://example.com"
	entryTitle := "title"
	entryContent := "content"
	tags := "tag1,tag2,tag3"

	tests := []struct {
		name           string
		username       string
		password       string
		clientID       string
		clientSecret   string
		tags           string
		onlyURL        bool
		entryURL       string
		entryTitle     string
		entryContent   string
		serverResponse func(w http.ResponseWriter, r *http.Request)
		wantErr        bool
		errContains    string
	}{
		{
			name:         "successful entry creation with url only",
			wantErr:      false,
			onlyURL:      true,
			username:     "username",
			password:     "password",
			clientID:     "clientId",
			clientSecret: "clientSecret",
			tags:         tags,
			entryURL:     entryURL,
			entryTitle:   entryTitle,
			entryContent: entryContent,
			serverResponse: func(w http.ResponseWriter, r *http.Request) {
				if strings.Contains(r.URL.Path, "/oauth/v2/token") {
					// Return success response
					w.WriteHeader(http.StatusOK)
					json.NewEncoder(w).Encode(map[string]any{
						"access_token":  "test-token",
						"expires_in":    3600,
						"refresh_token": "token",
						"scope":         "scope",
						"token_type":    "token_type",
					})
					return
				}
				// Verify authorization header
				auth := r.Header.Get("Authorization")
				if auth != "Bearer test-token" {
					t.Errorf("Expected Authorization header 'Bearer test-token', got %s", auth)
				}
				// Verify content type
				contentType := r.Header.Get("Content-Type")
				if contentType != "application/json" {
					t.Errorf("Expected Content-Type 'application/json', got %s", contentType)
				}
				// Parse and verify request
				body, _ := io.ReadAll(r.Body)
				var req map[string]any
				if err := json.Unmarshal(body, &req); err != nil {
					t.Errorf("Failed to parse request body: %v", err)
				}
				if requstEntryURL := req["url"]; requstEntryURL != entryURL {
					t.Errorf("Expected entryURL %s, got %s", entryURL, requstEntryURL)
				}
				if requestEntryTitle := req["title"]; requestEntryTitle != entryTitle {
					t.Errorf("Expected entryTitle %s, got %s", entryTitle, requestEntryTitle)
				}
				if _, ok := req["content"]; ok {
					t.Errorf("Expected entryContent to be empty, got value")
				}
				if requestTags := req["tags"]; requestTags != tags {
					t.Errorf("Expected tags %s, got %s", tags, requestTags)
				} // Return success response
				w.WriteHeader(http.StatusOK)
			},
			errContains: "",
		},
		{
			name:         "successful entry creation with content",
			wantErr:      false,
			onlyURL:      false,
			username:     "username",
			password:     "password",
			clientID:     "clientId",
			clientSecret: "clientSecret",
			tags:         tags,
			entryURL:     entryURL,
			entryTitle:   entryTitle,
			entryContent: entryContent,
			serverResponse: func(w http.ResponseWriter, r *http.Request) {
				if strings.Contains(r.URL.Path, "/oauth/v2/token") {
					// Return success response
					w.WriteHeader(http.StatusOK)
					json.NewEncoder(w).Encode(map[string]any{
						"access_token":  "test-token",
						"expires_in":    3600,
						"refresh_token": "token",
						"scope":         "scope",
						"token_type":    "token_type",
					})
					return
				}
				// Verify authorization header
				auth := r.Header.Get("Authorization")
				if auth != "Bearer test-token" {
					t.Errorf("Expected Authorization header 'Bearer test-token', got %s", auth)
				}
				// Verify content type
				contentType := r.Header.Get("Content-Type")
				if contentType != "application/json" {
					t.Errorf("Expected Content-Type 'application/json', got %s", contentType)
				}
				// Parse and verify request
				body, _ := io.ReadAll(r.Body)
				var req map[string]any
				if err := json.Unmarshal(body, &req); err != nil {
					t.Errorf("Failed to parse request body: %v", err)
				}
				if requstEntryURL := req["url"]; requstEntryURL != entryURL {
					t.Errorf("Expected entryURL %s, got %s", entryURL, requstEntryURL)
				}
				if requestEntryTitle := req["title"]; requestEntryTitle != entryTitle {
					t.Errorf("Expected entryTitle %s, got %s", entryTitle, requestEntryTitle)
				}
				if requestEntryContent := req["content"]; requestEntryContent != entryContent {
					t.Errorf("Expected entryContent %s, got %s", entryContent, requestEntryContent)
				}
				if requestTags := req["tags"]; requestTags != tags {
					t.Errorf("Expected tags %s, got %s", tags, requestTags)
				} // Return success response
				w.WriteHeader(http.StatusOK)
			},
			errContains: "",
		},
		{
			name:         "failed when unable to decode accessToken response",
			wantErr:      true,
			onlyURL:      true,
			username:     "username",
			password:     "password",
			clientID:     "clientId",
			clientSecret: "clientSecret",
			tags:         tags,
			entryURL:     entryURL,
			entryTitle:   entryTitle,
			entryContent: entryContent,
			serverResponse: func(w http.ResponseWriter, r *http.Request) {
				if strings.Contains(r.URL.Path, "/oauth/v2/token") {
					// Return success response
					w.WriteHeader(http.StatusOK)
					w.Write([]byte("invalid json"))
					return
				}
				t.Error("Server should not be called when failed to get accessToken")
			},
			errContains: "unable to decode token response",
		},
		{
			name:         "failed when saving entry",
			wantErr:      true,
			onlyURL:      true,
			username:     "username",
			password:     "password",
			clientID:     "clientId",
			clientSecret: "clientSecret",
			tags:         tags,
			entryURL:     entryURL,
			entryTitle:   entryTitle,
			entryContent: entryContent,
			serverResponse: func(w http.ResponseWriter, r *http.Request) {
				if strings.Contains(r.URL.Path, "/oauth/v2/token") {
					// Return success response
					w.WriteHeader(http.StatusOK)
					json.NewEncoder(w).Encode(map[string]any{
						"access_token":  "test-token",
						"expires_in":    3600,
						"refresh_token": "token",
						"scope":         "scope",
						"token_type":    "token_type",
					})
					return
				}
				w.WriteHeader(http.StatusUnauthorized)
			},
			errContains: "unable to save entry",
		},
		{
			name:         "failure due to no accessToken",
			wantErr:      true,
			onlyURL:      false,
			username:     "username",
			password:     "password",
			clientID:     "clientId",
			clientSecret: "clientSecret",
			tags:         tags,
			entryURL:     entryURL,
			entryTitle:   entryTitle,
			entryContent: entryContent,
			serverResponse: func(w http.ResponseWriter, r *http.Request) {
				if strings.Contains(r.URL.Path, "/oauth/v2/token") {
					// Return error response
					w.WriteHeader(http.StatusUnauthorized)
					return
				}
				t.Error("Server should not be called when failed to get accessToken")
			},
			errContains: "unable to get access token",
		},
		{
			name:         "failure due to missing client parameters",
			wantErr:      true,
			onlyURL:      false,
			tags:         tags,
			entryURL:     entryURL,
			entryTitle:   entryTitle,
			entryContent: entryContent,
			serverResponse: func(w http.ResponseWriter, r *http.Request) {
				t.Error("Server should not be called when failed to get accessToken")
			},
			errContains: "wallabag: missing base URL, client ID, client secret, username or password",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create test server if we have a server response function
			var serverURL string
			if tt.serverResponse != nil {
				server := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
				defer server.Close()
				serverURL = server.URL
			}

			// Create client with test server URL
			client := NewClient(serverURL, tt.clientID, tt.clientSecret, tt.username, tt.password, tt.tags, tt.onlyURL)

			// Call CreateBookmark
			err := client.CreateEntry(tt.entryURL, tt.entryTitle, tt.entryContent)

			// Check error expectations
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error but got none")
				} else if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
					t.Errorf("Expected error containing '%s', got '%s'", tt.errContains, err.Error())
				}
			} else {
				if err != nil {
					t.Errorf("Unexpected error: %v", err)
				}
			}
		})
	}
}

func TestNewClient(t *testing.T) {
	tests := []struct {
		name         string
		baseURL      string
		clientID     string
		clientSecret string
		username     string
		password     string
		tags         string
		onlyURL      bool
	}{
		{
			name:         "with all parameters",
			baseURL:      "https://wallabag.example.com",
			clientID:     "clientID",
			clientSecret: "clientSecret",
			username:     "wallabag",
			password:     "wallabag",
			tags:         "",
			onlyURL:      true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			client := NewClient(tt.baseURL, tt.clientID, tt.clientSecret, tt.username, tt.password, tt.tags, tt.onlyURL)

			if client.baseURL != tt.baseURL {
				t.Errorf("Expected.baseURL %s, got %s", tt.baseURL, client.baseURL)
			}
			if client.username != tt.username {
				t.Errorf("Expected username %s, got %s", tt.username, client.username)
			}
			if client.password != tt.password {
				t.Errorf("Expected password %s, got %s", tt.password, client.password)
			}
			if client.clientID != tt.clientID {
				t.Errorf("Expected clientID %s, got %s", tt.clientID, client.clientID)
			}
			if client.clientSecret != tt.clientSecret {
				t.Errorf("Expected clientSecret %s, got %s", tt.clientSecret, client.clientSecret)
			}
			if client.tags != tt.tags {
				t.Errorf("Expected tags %s, got %s", tt.tags, client.tags)
			}
			if client.onlyURL != tt.onlyURL {
				t.Errorf("Expected onlyURL %v, got %v", tt.onlyURL, client.onlyURL)
			}
		})
	}
}

func configureIntegrationAllowPrivateNetworksOption(t *testing.T) {
	t.Helper()

	t.Setenv("INTEGRATION_ALLOW_PRIVATE_NETWORKS", "1")

	configParser := config.NewConfigParser()
	parsedOptions, err := configParser.ParseEnvironmentVariables()
	if err != nil {
		t.Fatalf("Unable to configure test options: %v", err)
	}

	previousOptions := config.Opts
	config.Opts = parsedOptions
	t.Cleanup(func() {
		config.Opts = previousOptions
	})
}
