package main

import (
	"encoding/xml"
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
)

// Build-time configuration (set via ldflags at compile time)
var (
	BuildServerURL string
	BuildUsername  string
	BuildToken      string
)

// Config holds Nextcloud connection configuration
type Config struct {
	URL    string
	User   string
	Token  string
	WebDAV string
}

// Client is a Nextcloud WebDAV client
type Client struct {
	Config     Config
	HTTPClient *http.Client
}

// MultiStatus represents the WebDAV multistatus response
type MultiStatus struct {
	XMLName  xml.Name  `xml:"multistatus"`
	Responses []Response `xml:"response"`
}

// Response represents a single WebDAV response
type Response struct {
	Href     string     `xml:"href"`
	PropStats []PropStat `xml:"propstat"`
}

// PropStat represents a WebDAV propstat element
type PropStat struct {
	Prop   Prop   `xml:"prop"`
	Status string `xml:"status"`
}

// Prop represents WebDAV properties
type Prop struct {
	GetLastModified string `xml:"getlastmodified"`
	GetContentLength  int64  `xml:"getcontentlength"`
	GetContentType   string `xml:"getcontenttype"`
	ResourceType     ResourceType `xml:"resourcetype"`
	GetETag          string `xml:"getetag"`
}

// ResourceType represents the resourcetype element
type ResourceType struct {
	Collection string `xml:"collection"`
}

func main() {
	// Parse flags (with build-time defaults)
	serverURL := flag.String("url", BuildServerURL, "Nextcloud server URL (e.g., https://cloud.example.com)")
	username := flag.String("user", BuildUsername, "Nextcloud username")
	token := flag.String("token", BuildToken, "Nextcloud app token")
	operation := flag.String("op", "list", "Operation: list, upload, download, mkdir, delete, move, copy, info")
	path := flag.String("path", "/", "Remote path")
	localPath := flag.String("local", "", "Local path for upload/download")
	destPath := flag.String("dest", "", "Destination path for move/copy")
	recursive := flag.Bool("r", false, "Recursive operation")

	flag.Parse()

	// Use environment variables as fallback if not set at build time or via flags
	if *serverURL == "" {
		*serverURL = os.Getenv("NEXTCLOUD_URL")
	}
	if *username == "" {
		*username = os.Getenv("NEXTCLOUD_USER")
	}
	if *token == "" {
		*token = os.Getenv("NEXTCLOUD_TOKEN")
	}

	if serverURL == nil || username == nil || token == nil || *serverURL == "" || *username == "" || *token == "" {
		fmt.Println("Error: URL, user, and token are required (set via ldflags, flags, or env vars)")
		os.Exit(1)
	}

	// Build config
	encodedUsername := url.PathEscape(*username)
	config := Config{
		URL:    *serverURL,
		User:   *username,
		Token:  *token,
		WebDAV: fmt.Sprintf("%s/remote.php/dav/files/%s", strings.TrimSuffix(*serverURL, "/"), encodedUsername),
	}

	// Create client
	client := &Client{
		Config:     config,
		HTTPClient: &http.Client{},
	}

	var err error
	switch *operation {
	case "list":
		err = client.list(*path, *recursive)
	case "upload":
		err = client.upload(*localPath, *path)
	case "download":
		err = client.download(*path, *localPath)
	case "mkdir":
		err = client.mkdir(*path)
	case "delete":
		err = client.delete(*path)
	case "move":
		err = client.move(*path, *destPath)
	case "copy":
		err = client.copy(*path, *destPath)
	case "info":
		err = client.info(*path)
	default:
		fmt.Printf("Unknown operation: %s\n", *operation)
		os.Exit(1)
	}

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}
}

func (c *Client) list(path string, recursive bool) error {
	depth := "1"
	if recursive {
		depth = "infinity"
	}

	// PROPFIND body
	body := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
  <d:prop>
    <d:getlastmodified />
    <d:getcontentlength />
    <d:getcontenttype />
    <oc:permissions />
    <d:resourcetype />
    <d:getetag />
  </d:prop>
</d:propfind>`)

	req, err := http.NewRequest("PROPFIND", c.Config.WebDAV+path, strings.NewReader(body))
	if err != nil {
		return fmt.Errorf("request failed: %v", err)
	}

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("OCS-APIRequest", "true")
	req.Header.Set("Content-Type", "application/xml; charset=utf-8")
	req.Header.Set("Depth", depth)

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 207 {
		return fmt.Errorf("unexpected status: %d (expected 207 Multi-Status)", resp.StatusCode)
	}

	var multiStatus MultiStatus
	decoder := xml.NewDecoder(resp.Body)
	if err := decoder.Decode(&multiStatus); err != nil {
		return fmt.Errorf("xml decode failed: %v", err)
	}

	// Print results
	for _, response := range multiStatus.Responses {
		relPath := strings.TrimPrefix(response.Href, c.Config.WebDAV)
		if relPath == "" || relPath == c.Config.WebDAV+"/" {
			continue
		}

		// Merge props from successful propstats (200 OK)
		var mergedProp Prop
		isFolder := false

		for _, propStat := range response.PropStats {
			if strings.Contains(propStat.Status, "200") {
				// Merge successful props
				if propStat.Prop.GetLastModified != "" {
					mergedProp.GetLastModified = propStat.Prop.GetLastModified
				}
				if propStat.Prop.GetContentLength > 0 {
					mergedProp.GetContentLength = propStat.Prop.GetContentLength
				}
				if propStat.Prop.GetContentType != "" {
					mergedProp.GetContentType = propStat.Prop.GetContentType
				}
				if propStat.Prop.GetETag != "" {
					mergedProp.GetETag = propStat.Prop.GetETag
				}
				if propStat.Prop.ResourceType.Collection != "" {
					mergedProp.ResourceType = propStat.Prop.ResourceType
					isFolder = true
				}
			}
		}

		size := mergedProp.GetContentLength
		if isFolder {
			size = 0
		}

		indicator := "-"
		if isFolder {
			indicator = "d"
		}

		fmt.Printf("%s %-10d %s\n", indicator, size, relPath)
	}

	return nil
}

func (c *Client) upload(localPath, remotePath string) error {
	file, err := os.Open(localPath)
	if err != nil {
		return fmt.Errorf("failed to open local file: %v", err)
	}
	defer file.Close()

	stat, err := file.Stat()
	if err != nil {
		return fmt.Errorf("failed to stat file: %v", err)
	}

	req, err := http.NewRequest("PUT", c.Config.WebDAV+remotePath, file)
	if err != nil {
		return fmt.Errorf("request failed: %v", err)
	}

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("OCS-APIRequest", "true")
	req.ContentLength = stat.Size()

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 201 && resp.StatusCode != 200 {
		return fmt.Errorf("upload failed with status: %d", resp.StatusCode)
	}

	fmt.Printf("Uploaded %s to %s\n", localPath, remotePath)
	return nil
}

func (c *Client) download(remotePath, localPath string) error {
	req, err := http.NewRequest("GET", c.Config.WebDAV+remotePath, nil)
	if err != nil {
		return fmt.Errorf("request failed: %v", err)
	}

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("OCS-APIRequest", "true")

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return fmt.Errorf("download failed with status: %d", resp.StatusCode)
	}

	// Create parent directories if needed
	if err := os.MkdirAll(filepath.Dir(localPath), 0755); err != nil {
		return fmt.Errorf("failed to create local directory: %v", err)
	}

	outFile, err := os.Create(localPath)
	if err != nil {
		return fmt.Errorf("failed to create local file: %v", err)
	}
	defer outFile.Close()

	_, err = io.Copy(outFile, resp.Body)
	if err != nil {
		return fmt.Errorf("failed to copy file: %v", err)
	}

	fmt.Printf("Downloaded %s to %s\n", remotePath, localPath)
	return nil
}

func (c *Client) mkdir(path string) error {
	req, err := http.NewRequest("MKCOL", c.Config.WebDAV+path, nil)
	if err != nil {
		return fmt.Errorf("request failed: %v", err)
	}

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("OCS-APIRequest", "true")

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 201 && resp.StatusCode != 204 {
		return fmt.Errorf("mkdir failed with status: %d", resp.StatusCode)
	}

	fmt.Printf("Created directory: %s\n", path)
	return nil
}

func (c *Client) delete(path string) error {
	req, err := http.NewRequest("DELETE", c.Config.WebDAV+path, nil)
	if err != nil {
		return fmt.Errorf("request failed: %v", err)
	}

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("OCS-APIRequest", "true")

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 204 {
		return fmt.Errorf("delete failed with status: %d", resp.StatusCode)
	}

	fmt.Printf("Deleted: %s\n", path)
	return nil
}

func (c *Client) move(from, to string) error {
	fullURL := c.Config.WebDAV + from

	req, err := http.NewRequest("MOVE", fullURL, nil)
	if err != nil {
		return fmt.Errorf("request failed: %v", err)
	}

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("OCS-APIRequest", "true")

	dest := c.Config.WebDAV + to
	req.Header.Set("Destination", dest)

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 201 && resp.StatusCode != 204 {
		return fmt.Errorf("move failed with status: %d", resp.StatusCode)
	}

	fmt.Printf("Moved %s to %s\n", from, to)
	return nil
}

func (c *Client) copy(from, to string) error {
	fullURL := c.Config.WebDAV + from

	req, err := http.NewRequest("COPY", fullURL, nil)
	if err != nil {
		return fmt.Errorf("request failed: %v", err)
	}

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("OCS-APIRequest", "true")

	dest := c.Config.WebDAV + to
	req.Header.Set("Destination", dest)

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 201 {
		return fmt.Errorf("copy failed with status: %d", resp.StatusCode)
	}

	fmt.Printf("Copied %s to %s\n", from, to)
	return nil
}

func (c *Client) info(path string) error {
	depth := "0"

	// PROPFIND body
	body := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
  <d:prop>
    <d:getlastmodified />
    <d:getcontentlength />
    <d:getcontenttype />
    <oc:permissions />
    <d:resourcetype />
    <d:getetag />
  </d:prop>
</d:propfind>`)

	fullURL := c.Config.WebDAV + path
	req, err := http.NewRequest("PROPFIND", fullURL, strings.NewReader(body))
	if err != nil {
		return fmt.Errorf("request failed: %v", err)
	}

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("OCS-APIRequest", "true")
	req.Header.Set("Content-Type", "application/xml; charset=utf-8")
	req.Header.Set("Depth", depth)

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 207 {
		return fmt.Errorf("info failed with status: %d", resp.StatusCode)
	}

	var multiStatus MultiStatus
	decoder := xml.NewDecoder(resp.Body)
	if err := decoder.Decode(&multiStatus); err != nil {
		return fmt.Errorf("xml decode failed: %v", err)
	}

	// Print details
	for _, response := range multiStatus.Responses {
		relPath := strings.TrimPrefix(response.Href, c.Config.WebDAV)
		if relPath == "" || relPath == c.Config.WebDAV+"/" {
			continue
		}

		// Merge props from successful propstats (200 OK)
		var mergedProp Prop
		isFolder := false

		for _, propStat := range response.PropStats {
			if strings.Contains(propStat.Status, "200") {
				// Merge successful props
				if propStat.Prop.GetLastModified != "" {
					mergedProp.GetLastModified = propStat.Prop.GetLastModified
				}
				if propStat.Prop.GetContentLength > 0 {
					mergedProp.GetContentLength = propStat.Prop.GetContentLength
				}
				if propStat.Prop.GetContentType != "" {
					mergedProp.GetContentType = propStat.Prop.GetContentType
				}
				if propStat.Prop.GetETag != "" {
					mergedProp.GetETag = propStat.Prop.GetETag
				}
				if propStat.Prop.ResourceType.Collection != "" {
					mergedProp.ResourceType = propStat.Prop.ResourceType
					isFolder = true
				}
			}
		}

		size := mergedProp.GetContentLength
		if isFolder {
			size = 0
		}

		fmt.Printf("Path: %s\n", relPath)
		fmt.Printf("  Type: %s\n", map[bool]string{true: "Folder", false: "File"}[isFolder])
		fmt.Printf("  Size: %d bytes\n", size)
		fmt.Printf("  Modified: %s\n", mergedProp.GetLastModified)
		fmt.Printf("  ETag: %s\n", mergedProp.GetETag)
		if mergedProp.GetContentType != "" {
			fmt.Printf("  Type: %s\n", mergedProp.GetContentType)
		}
	}

	return nil
}

func (c *Client) request(method, path string, body io.Reader) (*http.Response, error) {
	fullURL := c.Config.WebDAV + path
	req, err := http.NewRequest(method, fullURL, body)
	if err != nil {
		return nil, fmt.Errorf("http request failed: %v", err)
	}

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("OCS-APIRequest", "true")

	// For PROPFIND, set Content-Type
	if method == "PROPFIND" {
		req.Header.Set("Content-Type", "application/xml; charset=utf-8")
	}

	return c.HTTPClient.Do(req)
}
