// Package client provides HTTP client functionality for interacting with the MCPJungle API.
package client

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"

	"github.com/mcpjungle/mcpjungle/internal/api"
	"github.com/mcpjungle/mcpjungle/pkg/types"
)

// APIError is a machine-readable API error returned by the MCPJungle server.
type APIError struct {
	Message string
	Code    string
}

func (e *APIError) Error() string {
	return e.Message
}

// Client represents a client for interacting with the MCPJungle HTTP API
type Client struct {
	baseURL     string
	accessToken string
	httpClient  *http.Client
}

func NewClient(baseURL string, accessToken string, httpClient *http.Client) *Client {
	return &Client{
		baseURL:     baseURL,
		accessToken: accessToken,
		httpClient:  httpClient,
	}
}

// BaseURL returns the base URL of the MCPJungle server
func (c *Client) BaseURL() string {
	return c.baseURL
}

// constructAPIEndpoint constructs the full API endpoint URL where a request must be sent
func (c *Client) constructAPIEndpoint(suffixPath string) (string, error) {
	return url.JoinPath(c.baseURL, api.V0ApiPathPrefix, suffixPath)
}

// newRequest creates a new HTTP request with the specified method, URL, and body.
// It automatically adds the Authorization header if an access token is present.
func (c *Client) newRequest(method, url string, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}
	if c.accessToken != "" {
		req.Header.Set("Authorization", "Bearer "+c.accessToken)
	}
	return req, nil
}

// parseErrorResponse parses HTTP error responses (4xx and 5xx) and returns a user-friendly error message
func (c *Client) parseErrorResponse(resp *http.Response) error {
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("request failed with status: %d (unable to read error details)", resp.StatusCode)
	}

	// For 4xx and 5xx status codes, try to parse as JSON error response
	if resp.StatusCode >= 400 && resp.StatusCode < 600 {
		var errorResp types.APIErrorResponse
		err := json.Unmarshal(body, &errorResp)
		if err != nil || errorResp.Error == "" {
			// If parsing as JSON fails or the error message is empty, return the raw response
			return fmt.Errorf("request failed with status: %d, message: %s", resp.StatusCode, string(body))
		}
		// Return the parsed error message
		return &APIError{Message: errorResp.Error, Code: errorResp.Code}
	}

	// For any other status code, return the full response
	return fmt.Errorf("unexpected response with status: %d, body: %s", resp.StatusCode, string(body))
}

// GetServerMetadata fetches metadata about the MCPJungle server.
func (c *Client) GetServerMetadata(ctx context.Context) (*types.ServerMetadata, error) {
	req, err := c.newRequest(http.MethodGet, c.baseURL+"/metadata", nil)
	if err != nil {
		return nil, err
	}
	req = req.WithContext(ctx)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, c.parseErrorResponse(resp)
	}

	var metadata types.ServerMetadata
	if err := json.NewDecoder(resp.Body).Decode(&metadata); err != nil {
		return nil, err
	}

	return &metadata, nil
}
