package nextcloud

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

// MountCloudResolver resolves provider web URLs for files on external mounts.
type MountCloudResolver struct {
	Client *http.Client
}

func (r *MountCloudResolver) httpClient() *http.Client {
	if r != nil && r.Client != nil {
		return r.Client
	}
	return &http.Client{Timeout: 45 * time.Second}
}

// ParseOAuthAccessToken extracts a bearer token from Nextcloud external storage token JSON.
func ParseOAuthAccessToken(raw string) string {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return ""
	}
	var obj map[string]any
	if err := json.Unmarshal([]byte(raw), &obj); err == nil {
		if at, ok := obj["access_token"].(string); ok && strings.TrimSpace(at) != "" {
			return strings.TrimSpace(at)
		}
	}
	return raw
}

func (r *MountCloudResolver) GoogleDriveWebViewLink(ctx context.Context, accessToken, fileID string) (string, error) {
	fileID = strings.TrimSpace(fileID)
	accessToken = strings.TrimSpace(accessToken)
	if fileID == "" || accessToken == "" {
		return "", fmt.Errorf("missing google file id or token")
	}
	apiURL := "https://www.googleapis.com/drive/v3/files/" + url.PathEscape(fileID) +
		"?fields=webViewLink,mimeType&supportsAllDrives=true"
	body, err := r.providerGET(ctx, accessToken, apiURL)
	if err != nil {
		return "", err
	}
	var parsed struct {
		WebViewLink string `json:"webViewLink"`
		MimeType    string `json:"mimeType"`
	}
	if err := json.Unmarshal(body, &parsed); err != nil {
		return "", err
	}
	if strings.TrimSpace(parsed.WebViewLink) != "" {
		return strings.TrimSpace(parsed.WebViewLink), nil
	}
	// Fallback when API omits webViewLink but mime is native Google Workspace.
	return driverootGoogleWorkspaceURL(parsed.MimeType, fileID), nil
}

func driverootGoogleWorkspaceURL(mimeType, fileID string) string {
	switch strings.TrimSpace(mimeType) {
	case "application/vnd.google-apps.document":
		return "https://docs.google.com/document/d/" + fileID + "/edit"
	case "application/vnd.google-apps.spreadsheet":
		return "https://docs.google.com/spreadsheets/d/" + fileID + "/edit"
	case "application/vnd.google-apps.presentation":
		return "https://docs.google.com/presentation/d/" + fileID + "/edit"
	default:
		return ""
	}
}

func (r *MountCloudResolver) ResolveGoogleDrivePath(ctx context.Context, accessToken, logicalPath string) (string, error) {
	segments := splitLogicalPathSegments(logicalPath)
	if len(segments) == 0 {
		return "", fmt.Errorf("empty path")
	}
	parentID := "root"
	for i, name := range segments {
		isLast := i == len(segments)-1
		id, mime, err := r.googleFindChild(ctx, accessToken, parentID, name, isLast)
		if err != nil {
			return "", err
		}
		if isLast {
			link, err := r.GoogleDriveWebViewLink(ctx, accessToken, id)
			if err != nil {
				if built := driverootGoogleWorkspaceURL(mime, id); built != "" {
					return built, nil
				}
				return "", err
			}
			return link, nil
		}
		parentID = id
	}
	return "", fmt.Errorf("path not found")
}

func (r *MountCloudResolver) googleFindChild(ctx context.Context, accessToken, parentID, name string, wantFile bool) (id, mimeType string, err error) {
	q := fmt.Sprintf("'%s' in parents and name = '%s' and trashed = false", parentID, escapeDriveQuery(name))
	if wantFile {
		q += " and mimeType != 'application/vnd.google-apps.folder'"
	} else {
		q += " and mimeType = 'application/vnd.google-apps.folder'"
	}
	listURL := "https://www.googleapis.com/drive/v3/files?supportsAllDrives=true&includeItemsFromAllDrives=true&fields=files(id,mimeType)&q=" +
		url.QueryEscape(q)
	body, err := r.providerGET(ctx, accessToken, listURL)
	if err != nil {
		return "", "", err
	}
	var parsed struct {
		Files []struct {
			ID       string `json:"id"`
			MimeType string `json:"mimeType"`
		} `json:"files"`
	}
	if err := json.Unmarshal(body, &parsed); err != nil {
		return "", "", err
	}
	if len(parsed.Files) == 0 {
		return "", "", fmt.Errorf("google drive item not found: %s", name)
	}
	return parsed.Files[0].ID, parsed.Files[0].MimeType, nil
}

func escapeDriveQuery(s string) string {
	return strings.ReplaceAll(s, "'", "\\'")
}

func (r *MountCloudResolver) ResolveMicrosoftDrivePath(ctx context.Context, accessToken, logicalPath string) (string, error) {
	segments := splitLogicalPathSegments(logicalPath)
	if len(segments) == 0 {
		return "", fmt.Errorf("empty path")
	}
	encoded := make([]string, len(segments))
	for i, seg := range segments {
		encoded[i] = url.PathEscape(seg)
	}
	itemPath := "/me/drive/root:/" + strings.Join(encoded, "/")
	apiURL := "https://graph.microsoft.com/v1.0" + itemPath + "?$select=webUrl"
	body, err := r.providerGET(ctx, accessToken, apiURL)
	if err != nil {
		return "", err
	}
	var parsed struct {
		WebURL string `json:"webUrl"`
	}
	if err := json.Unmarshal(body, &parsed); err != nil {
		return "", err
	}
	webURL := strings.TrimSpace(parsed.WebURL)
	if webURL == "" {
		return "", fmt.Errorf("microsoft drive item has no webUrl")
	}
	return webURL, nil
}

func splitLogicalPathSegments(logicalPath string) []string {
	logicalPath = strings.Trim(logicalPath, "/")
	if logicalPath == "" {
		return nil
	}
	return strings.Split(logicalPath, "/")
}

func (r *MountCloudResolver) providerGET(ctx context.Context, accessToken, apiURL string) ([]byte, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+accessToken)
	resp, err := r.httpClient().Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
	if err != nil {
		return nil, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("provider api %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
	}
	return body, nil
}
