package nextcloud

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

const propfindFileProps = `<?xml version="1.0" encoding="UTF-8"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
  <d:prop>
    <d:getlastmodified/>
    <d:getetag/>
    <d:getcontenttype/>
    <d:getcontentlength/>
    <d:resourcetype/>
    <oc:fileid/>
    <oc:size/>
    <oc:favorite/>
    <oc:share-types/>
    <d:displayname/>
  </d:prop>
</d:propfind>`

func (c *Client) StatFile(ctx context.Context, userID, filePath string) (FileInfo, error) {
	filePath = NormalizeClientFilePath(userID, filePath)
	davPath := c.WebDAVPath(userID, filePath)

	resp, err := c.DoAsUser(ctx, "PROPFIND", davPath, strings.NewReader(propfindFileProps), userID, map[string]string{
		"Depth":        "0",
		"Content-Type": "application/xml",
	})
	if err != nil {
		return FileInfo{}, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusMultiStatus && resp.StatusCode != http.StatusOK {
		return FileInfo{}, &HTTPStatusError{Operation: "propfind stat file", StatusCode: resp.StatusCode}
	}

	return parseSinglePropfindResponse(resp.Body)
}

func (c *Client) FindFileByID(ctx context.Context, userID string, fileID int64) (FileInfo, error) {
	if fileID <= 0 {
		return FileInfo{}, fmt.Errorf("invalid file id")
	}
	userSeg := strings.TrimSpace(userID)
	searchBody := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<d:searchrequest xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
  <d:basicsearch>
    <d:select>
      <d:prop>
        <oc:fileid/>
        <d:displayname/>
        <d:getcontenttype/>
        <d:getcontentlength/>
        <d:getlastmodified/>
        <d:getetag/>
        <d:resourcetype/>
        <oc:size/>
        <oc:favorite/>
        <oc:share-types/>
      </d:prop>
    </d:select>
    <d:from>
      <d:scope>
        <d:href>/files/%s</d:href>
        <d:depth>infinity</d:depth>
      </d:scope>
    </d:from>
    <d:where>
      <d:eq>
        <d:prop><oc:fileid/></d:prop>
        <d:literal>%d</d:literal>
      </d:eq>
    </d:where>
    <d:orderby/>
    <d:limit><d:nresults>1</d:nresults></d:limit>
  </d:basicsearch>
</d:searchrequest>`, userSeg, fileID)

	resp, err := c.DoAsUser(ctx, "SEARCH", "/remote.php/dav/", strings.NewReader(searchBody), userID, map[string]string{
		"Content-Type": "text/xml",
	})
	if err != nil {
		return FileInfo{}, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusMultiStatus && resp.StatusCode != http.StatusOK {
		return FileInfo{}, &HTTPStatusError{Operation: "search file by id", StatusCode: resp.StatusCode}
	}

	return parseSinglePropfindResponse(resp.Body)
}

func parseSinglePropfindResponse(body io.Reader) (FileInfo, error) {
	var ms multistatus
	if err := xml.NewDecoder(body).Decode(&ms); err != nil {
		return FileInfo{}, err
	}
	if len(ms.Responses) == 0 {
		return FileInfo{}, fmt.Errorf("file not found")
	}
	return fileInfoFromDAVResponse(ms.Responses[0]), nil
}

func fileInfoFromDAVResponse(r response) FileInfo {
	name := fileNameFromDAVProp(r.Propstat.Prop.DisplayName, r.Href)
	clientPath := clientPathFromDAVHref(r.Href)
	name = SyncFileDisplayName(clientPath, name)

	fileType := "file"
	if r.Propstat.Prop.ResourceType.Collection != nil {
		fileType = "directory"
	}

	size := r.Propstat.Prop.ContentLength
	if r.Propstat.Prop.Size > 0 {
		size = r.Propstat.Prop.Size
	}

	return FileInfo{
		Path:         clientPath,
		Name:         name,
		Type:         fileType,
		Size:         size,
		MimeType:     r.Propstat.Prop.ContentType,
		LastModified: r.Propstat.Prop.LastModified,
		ETag:         strings.Trim(r.Propstat.Prop.ETag, "\""),
		FileID:       parseFileID(r.Propstat.Prop.FileID),
		IsFavorite:   r.Propstat.Prop.Favorite == 1,
		IsShared:     len(r.Propstat.Prop.ShareTypes.ShareType) > 0,
	}
}

func ParseDriveFileID(raw string) (int64, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return 0, fmt.Errorf("empty file id")
	}
	id, err := strconv.ParseInt(raw, 10, 64)
	if err != nil || id <= 0 {
		return 0, fmt.Errorf("invalid file id %q", raw)
	}
	return id, nil
}
