package nextcloud

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

type ExternalMount struct {
	ID         int    `json:"id"`
	MountPoint string `json:"mount_point"`
	Backend    string `json:"backend"`
	Status     int    `json:"status"`
}

type WebDAVMountConfig struct {
	Host     string `json:"host"`
	Root     string `json:"root"`
	User     string `json:"user"`
	Password string `json:"password"`
	Secure   bool   `json:"secure"`
}

// CreateUserWebDAVMount registers a WebDAV external storage mount for a user.
func (c *Client) CreateUserWebDAVMount(ctx context.Context, userID, mountPoint string, cfg WebDAVMountConfig) (int, error) {
	return c.createExternalMount(ctx, mountPoint, "dav", "password::password", userID, map[string]string{
		"host":     cfg.Host,
		"root":     cfg.Root,
		"user":     cfg.User,
		"password": cfg.Password,
		"secure":   boolString(cfg.Secure),
	})
}

// CreateGlobalWebDAVMount registers an org-wide WebDAV mount (all users).
func (c *Client) CreateGlobalWebDAVMount(ctx context.Context, mountPoint string, cfg WebDAVMountConfig) (int, error) {
	return c.createExternalMount(ctx, mountPoint, "dav", "password::password", "", map[string]string{
		"host":     cfg.Host,
		"root":     cfg.Root,
		"user":     cfg.User,
		"password": cfg.Password,
		"secure":   boolString(cfg.Secure),
	})
}

func boolString(v bool) string {
	if v {
		return "true"
	}
	return "false"
}

func (c *Client) createExternalMount(ctx context.Context, mountPoint, backend, authBackend, userID string, config map[string]string) (int, error) {
	form := url.Values{}
	form.Set("mountPoint", mountPoint)
	form.Set("backend", backend)
	form.Set("authBackend", authBackend)
	if userID != "" {
		form.Set("user", userID)
	}
	for k, v := range config {
		form.Set("config["+k+"]", v)
	}
	apiPath := "/index.php/apps/files_external/api/v1/mounts?format=json"
	resp, err := c.doRequest(ctx, "POST", apiPath, strings.NewReader(form.Encode()), map[string]string{
		"Content-Type": "application/x-www-form-urlencoded",
	})
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return 0, fmt.Errorf("create external mount: %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
	}
	var payload struct {
		OCS struct {
			Data struct {
				ID int `json:"id"`
			} `json:"data"`
		} `json:"ocs"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
		return 0, err
	}
	if payload.OCS.Data.ID <= 0 {
		return 0, fmt.Errorf("external mount create returned empty id")
	}
	return payload.OCS.Data.ID, nil
}

func (c *Client) DeleteExternalMount(ctx context.Context, mountID int) error {
	apiPath := fmt.Sprintf("/index.php/apps/files_external/api/v1/mounts/%d?format=json", mountID)
	resp, err := c.doRequest(ctx, "DELETE", apiPath, nil, ocsJSONHeaders())
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return &HTTPStatusError{Operation: "delete external mount", StatusCode: resp.StatusCode}
	}
	return nil
}

func (c *Client) ListUserExternalMounts(ctx context.Context, userID string) ([]ExternalMount, error) {
	apiPath := "/index.php/apps/files_external/api/v1/mounts?format=json"
	resp, err := c.DoAsUser(ctx, "GET", apiPath, nil, userID, ocsJSONHeaders())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, &HTTPStatusError{Operation: "list external mounts", StatusCode: resp.StatusCode}
	}
	return decodeExternalMounts(resp.Body)
}

func (c *Client) ListGlobalExternalMounts(ctx context.Context) ([]ExternalMount, error) {
	apiPath := "/index.php/apps/files_external/globalstorages?format=json"
	resp, err := c.doRequest(ctx, "GET", apiPath, nil, ocsJSONHeaders())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, &HTTPStatusError{Operation: "list global external mounts", StatusCode: resp.StatusCode}
	}
	return decodeExternalMounts(resp.Body)
}

func decodeExternalMounts(body io.Reader) ([]ExternalMount, error) {
	var payload struct {
		OCS struct {
			Data json.RawMessage `json:"data"`
		} `json:"ocs"`
	}
	if err := json.NewDecoder(body).Decode(&payload); err != nil {
		return nil, err
	}
	raw := payload.OCS.Data
	if len(raw) == 0 || string(raw) == "null" {
		return nil, nil
	}
	var mounts []ExternalMount
	if err := json.Unmarshal(raw, &mounts); err == nil {
		return mounts, nil
	}
	var asMap map[string]ExternalMount
	if err := json.Unmarshal(raw, &asMap); err != nil {
		return nil, err
	}
	out := make([]ExternalMount, 0, len(asMap))
	for _, m := range asMap {
		out = append(out, m)
	}
	return out, nil
}

// CreateOAuthExternalMount creates a mount using OAuth2 backend (Google, Dropbox, etc.).
func (c *Client) CreateOAuthExternalMount(ctx context.Context, userID, mountPoint, backend, authBackend string, oauthConfig map[string]string) (int, error) {
	config := map[string]string{
		"configured": "false",
		"token":      "",
	}
	for k, v := range oauthConfig {
		config[k] = v
	}
	return c.createExternalMount(ctx, mountPoint, backend, authBackend, userID, config)
}

type OAuth2StepResult struct {
	URL   string
	Token string
}

func (c *Client) StartExternalStorageOAuth2(ctx context.Context, userID, clientID, clientSecret, redirectURI string) (string, error) {
	result, err := c.postExternalStorageOAuth2(ctx, userID, map[string]string{
		"step":          "1",
		"client_id":     clientID,
		"client_secret": clientSecret,
		"redirect":      redirectURI,
	})
	if err != nil {
		return "", err
	}
	if result.URL == "" {
		return "", fmt.Errorf("oauth2 step 1: empty authorization url")
	}
	return result.URL, nil
}

func (c *Client) CompleteExternalStorageOAuth2(ctx context.Context, userID, clientID, clientSecret, redirectURI, code string) (string, error) {
	result, err := c.postExternalStorageOAuth2(ctx, userID, map[string]string{
		"step":          "2",
		"client_id":     clientID,
		"client_secret": clientSecret,
		"redirect":      redirectURI,
		"code":          code,
	})
	if err != nil {
		return "", err
	}
	if result.Token == "" {
		return "", fmt.Errorf("oauth2 step 2: empty token")
	}
	return result.Token, nil
}

func (c *Client) UpdateUserExternalMountOAuth(ctx context.Context, userID string, mountID int, clientID, clientSecret, token string) error {
	form := url.Values{}
	form.Set("client_id", clientID)
	form.Set("client_secret", clientSecret)
	form.Set("token", token)
	form.Set("configured", "true")
	apiPath := fmt.Sprintf("/index.php/apps/files_external/userstorages/%d?format=json", mountID)
	resp, err := c.DoAsUser(ctx, "PUT", apiPath, strings.NewReader(form.Encode()), userID, map[string]string{
		"Content-Type": "application/x-www-form-urlencoded",
	})
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return fmt.Errorf("update external mount oauth: %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
	}
	return nil
}

func (c *Client) postExternalStorageOAuth2(ctx context.Context, userID string, fields map[string]string) (OAuth2StepResult, error) {
	form := url.Values{}
	for k, v := range fields {
		form.Set(k, v)
	}
	apiPath := "/index.php/apps/files_external/ajax/oauth2.php"
	resp, err := c.DoAsUser(ctx, "POST", apiPath, strings.NewReader(form.Encode()), userID, map[string]string{
		"Content-Type": "application/x-www-form-urlencoded",
	})
	if err != nil {
		return OAuth2StepResult{}, err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(io.LimitReader(resp.Body, 8192))
	if err != nil {
		return OAuth2StepResult{}, err
	}
	if resp.StatusCode != http.StatusOK {
		return OAuth2StepResult{}, fmt.Errorf("oauth2 request: %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
	}
	var payload struct {
		Status string `json:"status"`
		Data   struct {
			URL     string `json:"url"`
			Token   string `json:"token"`
			Message string `json:"message"`
		} `json:"data"`
	}
	if err := json.Unmarshal(body, &payload); err != nil {
		return OAuth2StepResult{}, fmt.Errorf("oauth2 response decode: %w", err)
	}
	if payload.Status != "success" {
		msg := strings.TrimSpace(payload.Data.Message)
		if msg == "" {
			msg = strings.TrimSpace(string(body))
		}
		return OAuth2StepResult{}, fmt.Errorf("oauth2 failed: %s", msg)
	}
	return OAuth2StepResult{URL: payload.Data.URL, Token: payload.Data.Token}, nil
}

func ParseMountID(raw string) (int, error) {
	return strconv.Atoi(strings.TrimSpace(raw))
}
