package nextcloud

import (
	"encoding/json"
	"fmt"
	"io"
	"strings"
	"time"
)

type ocsShareRecord struct {
	ID                   flexShareID `json:"id"`
	Path                 string      `json:"path"`
	ShareType            int         `json:"share_type"`
	Permissions          int         `json:"permissions"`
	URL                  string      `json:"url"`
	Expiration           string      `json:"expiration"`
	ShareWith            *string     `json:"share_with"`
	ShareWithDisplayName string      `json:"share_with_displayname"`
	Label                string      `json:"label"`
	Token                string      `json:"token"`
	UIDOwner             string      `json:"uid_owner"`
	DisplayNameOwner     string      `json:"displayname_owner"`
	UIDFileOwner         string      `json:"uid_file_owner"`
	DisplayNameFileOwner string      `json:"displayname_file_owner"`
	Stime                int64       `json:"stime"`
	Note                 string      `json:"note"`
	ItemType             string      `json:"item_type"`
	Password             *string     `json:"password"`
}

// flexShareID accepts Nextcloud share ids as JSON string or number.
type flexShareID string

func (f *flexShareID) UnmarshalJSON(b []byte) error {
	var s string
	if err := json.Unmarshal(b, &s); err == nil {
		*f = flexShareID(strings.TrimSpace(s))
		return nil
	}
	var n json.Number
	if err := json.Unmarshal(b, &n); err == nil {
		*f = flexShareID(n.String())
		return nil
	}
	return fmt.Errorf("invalid share id")
}

func (f flexShareID) String() string {
	return string(f)
}

func decodeOCSShareRecords(raw json.RawMessage) ([]ocsShareRecord, error) {
	raw = json.RawMessage(strings.TrimSpace(string(raw)))
	if len(raw) == 0 || string(raw) == "null" || string(raw) == "[]" {
		return nil, nil
	}
	if raw[0] == '[' {
		var list []ocsShareRecord
		if err := json.Unmarshal(raw, &list); err != nil {
			return nil, err
		}
		return list, nil
	}
	var one ocsShareRecord
	if err := json.Unmarshal(raw, &one); err != nil {
		return nil, err
	}
	return []ocsShareRecord{one}, nil
}

func decodeOCSShareResponse(body io.Reader, fallbackPath string) (*ShareInfo, error) {
	var ocsResp struct {
		OCS struct {
			Data json.RawMessage `json:"data"`
		} `json:"ocs"`
	}
	if err := json.NewDecoder(body).Decode(&ocsResp); err != nil {
		return nil, err
	}
	items, err := decodeOCSShareRecords(ocsResp.OCS.Data)
	if err != nil {
		return nil, err
	}
	if len(items) == 0 {
		return nil, fmt.Errorf("empty share response")
	}
	info := mapOCSShareRecord(items[0], fallbackPath)
	return &info, nil
}

func mapOCSShareRecord(item ocsShareRecord, fallbackPath string) ShareInfo {
	path := strings.TrimSpace(item.Path)
	if path == "" {
		path = fallbackPath
	}
	shareWith := ""
	if item.ShareWith != nil {
		shareWith = strings.TrimSpace(*item.ShareWith)
	}
	expiration := strings.TrimSpace(item.Expiration)
	info := ShareInfo{
		ID:                   item.ID.String(),
		Path:                 path,
		ShareType:            item.ShareType,
		Permissions:          item.Permissions,
		URL:                  item.URL,
		ExpiresAt:            expiration,
		ShareWith:            shareWith,
		ShareWithDisplayName: strings.TrimSpace(item.ShareWithDisplayName),
		Label:                strings.TrimSpace(item.Label),
		Token:                strings.TrimSpace(item.Token),
		OwnerID:              strings.TrimSpace(item.UIDOwner),
		OwnerDisplayName:     strings.TrimSpace(item.DisplayNameOwner),
		FileOwnerID:          strings.TrimSpace(item.UIDFileOwner),
		FileOwnerDisplayName: strings.TrimSpace(item.DisplayNameFileOwner),
		Note:                 strings.TrimSpace(item.Note),
		ItemType:             strings.TrimSpace(item.ItemType),
		HasPassword:          item.Password != nil && strings.TrimSpace(*item.Password) != "",
	}
	if ts := item.Stime; ts > 0 {
		info.CreatedAt = time.Unix(ts, 0).UTC().Format(time.RFC3339)
	}
	if item.ShareType == 3 {
		if strings.EqualFold(info.Label, "internal") {
			info.AccessMode = "internal"
		} else {
			info.AccessMode = "public"
		}
	}
	return info
}
