package nextcloud

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"
)

type Client struct {
	baseURL         string
	publicURL       string
	drivePublicURL  string
	httpClient      *http.Client
	adminUser       string
	adminPass       string
	credStore       *DAVCredentialStore
}

func NewClient(baseURL, adminUser, adminPass string) *Client {
	return &Client{
		baseURL: strings.TrimRight(strings.TrimSpace(baseURL), "/"),
		httpClient: &http.Client{
			Timeout: 30 * time.Second,
		},
		adminUser: adminUser,
		adminPass: adminPass,
	}
}

func (c *Client) WithPublicURL(publicURL string) *Client {
	if c == nil {
		return nil
	}
	c.publicURL = strings.TrimRight(strings.TrimSpace(publicURL), "/")
	return c
}

func (c *Client) WithDAVCredentials(store *DAVCredentialStore) *Client {
	if c == nil {
		return nil
	}
	c.credStore = store
	return c
}

// webDAVDestination builds the Destination header for MOVE/COPY.
// Nextcloud validates this against OVERWRITECLIURL (e.g. http://localhost/cloud).
func (c *Client) webDAVDestination(davPath string) string {
	if !strings.HasPrefix(davPath, "/") {
		davPath = "/" + davPath
	}
	if c.publicURL != "" {
		return c.publicURL + davPath
	}
	return SameServerDestinationHeader(davPath)
}

func (c *Client) doRequest(ctx context.Context, method, path string, body io.Reader, headers map[string]string) (*http.Response, error) {
	url := c.baseURL + path
	req, err := http.NewRequestWithContext(ctx, method, url, body)
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth(c.adminUser, c.adminPass)
	req.Header.Set("OCS-APIRequest", "true")
	for k, v := range headers {
		req.Header.Set(k, v)
	}

	return c.httpClient.Do(req)
}

func (c *Client) DoAsUser(ctx context.Context, method, path string, body io.Reader, userID string, headers map[string]string) (*http.Response, error) {
	return c.doAsUser(ctx, method, path, body, userID, headers, false)
}

func (c *Client) doAsUser(ctx context.Context, method, path string, body io.Reader, userID string, headers map[string]string, retried bool) (*http.Response, error) {
	token, err := c.userDAVToken(ctx, userID)
	if err != nil {
		return nil, err
	}

	url := c.baseURL + path
	req, err := http.NewRequestWithContext(ctx, method, url, body)
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth(userID, token)
	req.Header.Set("OCS-APIRequest", "true")
	for k, v := range headers {
		req.Header.Set(k, v)
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == http.StatusUnauthorized && c.credStore != nil {
		resp.Body.Close()
		_ = c.credStore.DeleteToken(ctx, userID)
		if !retried {
			if refreshErr := c.RefreshPrincipalCredentials(ctx, userID); refreshErr == nil {
				return c.doAsUser(ctx, method, path, body, userID, headers, true)
			}
		}
		return nil, ErrDAVCredentialsMissing
	}
	return resp, nil
}

func (c *Client) userDAVToken(ctx context.Context, userID string) (string, error) {
	if c.credStore == nil {
		return "", fmt.Errorf("nextcloud dav credentials store not configured")
	}
	token, err := c.credStore.GetToken(ctx, userID)
	if err != nil {
		return "", err
	}
	return token, nil
}

