// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2025 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package happydns_test

import (
	"errors"
	"net/http"
	"testing"

	"git.happydns.org/happyDomain/model"
)

func TestCustomErrorError(t *testing.T) {
	baseErr := errors.New("test error message")
	customErr := happydns.CustomError{
		Err:      baseErr,
		UserLink: "https://example.com/help",
		Status:   http.StatusBadRequest,
	}

	if customErr.Error() != "test error message" {
		t.Errorf("CustomError.Error() = %q; want %q", customErr.Error(), "test error message")
	}
}

func TestCustomErrorToErrorResponse(t *testing.T) {
	baseErr := errors.New("custom error")
	customErr := happydns.CustomError{
		Err:      baseErr,
		UserLink: "https://example.com/docs",
		Status:   http.StatusBadRequest,
	}

	resp := customErr.ToErrorResponse()

	if resp.Message != "custom error" {
		t.Errorf("ToErrorResponse().Message = %q; want %q", resp.Message, "custom error")
	}

	if resp.Link != "https://example.com/docs" {
		t.Errorf("ToErrorResponse().Link = %q; want %q", resp.Link, "https://example.com/docs")
	}
}

func TestCustomErrorHTTPStatus(t *testing.T) {
	tests := []struct {
		name           string
		status         int
		expectedStatus int
	}{
		{
			name:           "bad request",
			status:         http.StatusBadRequest,
			expectedStatus: http.StatusBadRequest,
		},
		{
			name:           "not found",
			status:         http.StatusNotFound,
			expectedStatus: http.StatusNotFound,
		},
		{
			name:           "internal server error",
			status:         http.StatusInternalServerError,
			expectedStatus: http.StatusInternalServerError,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			customErr := happydns.CustomError{
				Err:    errors.New("test"),
				Status: tt.status,
			}

			if customErr.HTTPStatus() != tt.expectedStatus {
				t.Errorf("HTTPStatus() = %d; want %d", customErr.HTTPStatus(), tt.expectedStatus)
			}
		})
	}
}

func TestForbiddenErrorError(t *testing.T) {
	forbiddenErr := happydns.ForbiddenError{
		Msg: "access denied",
	}

	if forbiddenErr.Error() != "access denied" {
		t.Errorf("ForbiddenError.Error() = %q; want %q", forbiddenErr.Error(), "access denied")
	}
}

func TestForbiddenErrorToErrorResponse(t *testing.T) {
	forbiddenErr := happydns.ForbiddenError{
		Msg: "forbidden resource",
	}

	resp := forbiddenErr.ToErrorResponse()

	if resp.Message != "forbidden resource" {
		t.Errorf("ToErrorResponse().Message = %q; want %q", resp.Message, "forbidden resource")
	}

	if resp.Link != "" {
		t.Errorf("ToErrorResponse().Link = %q; want empty string", resp.Link)
	}
}

func TestForbiddenErrorHTTPStatus(t *testing.T) {
	forbiddenErr := happydns.ForbiddenError{
		Msg: "test",
	}

	if forbiddenErr.HTTPStatus() != http.StatusForbidden {
		t.Errorf("HTTPStatus() = %d; want %d", forbiddenErr.HTTPStatus(), http.StatusForbidden)
	}
}

func TestInternalErrorError(t *testing.T) {
	baseErr := errors.New("internal error message")
	internalErr := happydns.InternalError{
		Err:         baseErr,
		UserMessage: "something went wrong",
		UserLink:    "https://example.com/status",
	}

	if internalErr.Error() != "internal error message" {
		t.Errorf("InternalError.Error() = %q; want %q", internalErr.Error(), "internal error message")
	}
}

func TestInternalErrorToErrorResponse(t *testing.T) {
	tests := []struct {
		name            string
		err             error
		userMessage     string
		userLink        string
		expectedMessage string
		expectedLink    string
	}{
		{
			name:            "with user message",
			err:             errors.New("internal error"),
			userMessage:     "user-friendly message",
			userLink:        "https://example.com/help",
			expectedMessage: "user-friendly message",
			expectedLink:    "https://example.com/help",
		},
		{
			name:            "without user message",
			err:             errors.New("internal error"),
			userMessage:     "",
			userLink:        "https://example.com/help",
			expectedMessage: "internal error",
			expectedLink:    "https://example.com/help",
		},
		{
			name:            "no link",
			err:             errors.New("error"),
			userMessage:     "user message",
			userLink:        "",
			expectedMessage: "user message",
			expectedLink:    "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			internalErr := happydns.InternalError{
				Err:         tt.err,
				UserMessage: tt.userMessage,
				UserLink:    tt.userLink,
			}

			resp := internalErr.ToErrorResponse()

			if resp.Message != tt.expectedMessage {
				t.Errorf("ToErrorResponse().Message = %q; want %q", resp.Message, tt.expectedMessage)
			}

			if resp.Link != tt.expectedLink {
				t.Errorf("ToErrorResponse().Link = %q; want %q", resp.Link, tt.expectedLink)
			}
		})
	}
}

func TestInternalErrorHTTPStatus(t *testing.T) {
	internalErr := happydns.InternalError{
		Err: errors.New("test"),
	}

	if internalErr.HTTPStatus() != http.StatusInternalServerError {
		t.Errorf("HTTPStatus() = %d; want %d", internalErr.HTTPStatus(), http.StatusInternalServerError)
	}
}

func TestNotFoundErrorError(t *testing.T) {
	notFoundErr := happydns.NotFoundError{
		Msg: "resource not found",
	}

	if notFoundErr.Error() != "resource not found" {
		t.Errorf("NotFoundError.Error() = %q; want %q", notFoundErr.Error(), "resource not found")
	}
}

func TestNotFoundErrorToErrorResponse(t *testing.T) {
	notFoundErr := happydns.NotFoundError{
		Msg: "page not found",
	}

	resp := notFoundErr.ToErrorResponse()

	if resp.Message != "page not found" {
		t.Errorf("ToErrorResponse().Message = %q; want %q", resp.Message, "page not found")
	}

	if resp.Link != "" {
		t.Errorf("ToErrorResponse().Link = %q; want empty string", resp.Link)
	}
}

func TestNotFoundErrorHTTPStatus(t *testing.T) {
	notFoundErr := happydns.NotFoundError{
		Msg: "test",
	}

	if notFoundErr.HTTPStatus() != http.StatusNotFound {
		t.Errorf("HTTPStatus() = %d; want %d", notFoundErr.HTTPStatus(), http.StatusNotFound)
	}
}

func TestValidationErrorError(t *testing.T) {
	validationErr := happydns.ValidationError{
		Msg: "validation failed",
	}

	if validationErr.Error() != "validation failed" {
		t.Errorf("ValidationError.Error() = %q; want %q", validationErr.Error(), "validation failed")
	}
}

func TestValidationErrorToErrorResponse(t *testing.T) {
	validationErr := happydns.ValidationError{
		Msg: "invalid input",
	}

	resp := validationErr.ToErrorResponse()

	if resp.Message != "invalid input" {
		t.Errorf("ToErrorResponse().Message = %q; want %q", resp.Message, "invalid input")
	}

	if resp.Link != "" {
		t.Errorf("ToErrorResponse().Link = %q; want empty string", resp.Link)
	}
}

func TestValidationErrorHTTPStatus(t *testing.T) {
	validationErr := happydns.ValidationError{
		Msg: "test",
	}

	if validationErr.HTTPStatus() != http.StatusBadRequest {
		t.Errorf("HTTPStatus() = %d; want %d", validationErr.HTTPStatus(), http.StatusBadRequest)
	}
}

func TestHTTPErrorInterface(t *testing.T) {
	tests := []struct {
		name     string
		err      happydns.HTTPError
		wantCode int
	}{
		{
			name: "CustomError",
			err: happydns.CustomError{
				Err:    errors.New("test"),
				Status: http.StatusTeapot,
			},
			wantCode: http.StatusTeapot,
		},
		{
			name: "ForbiddenError",
			err: happydns.ForbiddenError{
				Msg: "forbidden",
			},
			wantCode: http.StatusForbidden,
		},
		{
			name: "InternalError",
			err: happydns.InternalError{
				Err: errors.New("test"),
			},
			wantCode: http.StatusInternalServerError,
		},
		{
			name: "NotFoundError",
			err: happydns.NotFoundError{
				Msg: "not found",
			},
			wantCode: http.StatusNotFound,
		},
		{
			name: "ValidationError",
			err: happydns.ValidationError{
				Msg: "invalid",
			},
			wantCode: http.StatusBadRequest,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.err.HTTPStatus() != tt.wantCode {
				t.Errorf("HTTPStatus() = %d; want %d", tt.err.HTTPStatus(), tt.wantCode)
			}

			resp := tt.err.ToErrorResponse()
			if resp.Message == "" {
				t.Error("ToErrorResponse().Message should not be empty")
			}
		})
	}
}

func TestPredefinedErrors(t *testing.T) {
	tests := []struct {
		name string
		err  error
		msg  string
	}{
		{
			name: "ErrAuthUserNotFound",
			err:  happydns.ErrAuthUserNotFound,
			msg:  "auth user not found",
		},
		{
			name: "ErrDomainNotFound",
			err:  happydns.ErrDomainNotFound,
			msg:  "domain not found",
		},
		{
			name: "ErrDomainLogNotFound",
			err:  happydns.ErrDomainLogNotFound,
			msg:  "domain log not found",
		},
		{
			name: "ErrProviderNotFound",
			err:  happydns.ErrProviderNotFound,
			msg:  "provider not found",
		},
		{
			name: "ErrSessionNotFound",
			err:  happydns.ErrSessionNotFound,
			msg:  "session not found",
		},
		{
			name: "ErrUserNotFound",
			err:  happydns.ErrUserNotFound,
			msg:  "user not found",
		},
		{
			name: "ErrUserAlreadyExist",
			err:  happydns.ErrUserAlreadyExist,
			msg:  "user already exists",
		},
		{
			name: "ErrZoneNotFound",
			err:  happydns.ErrZoneNotFound,
			msg:  "zone not found",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.err.Error() != tt.msg {
				t.Errorf("Error message = %q; want %q", tt.err.Error(), tt.msg)
			}
		})
	}
}
