package main

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

// 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
	CalDAV string
}

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

// MultiStatus represents 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 {
	DisplayName string `xml:"displayname"`
	GetETag     string `xml:"getetag"`
	ResourceType ResourceType `xml:"resourcetype"`
}

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

// Calendar represents a CalDAV calendar
type Calendar struct {
	Href       string
	DisplayName string
	ETag       string
}

// Event represents a calendar event (iCalendar format)
type Event struct {
	UID     string
	Summary string
	Start   time.Time
	End     time.Time
	iCal    string // Raw iCalendar content
}

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-calendars", "Operation: list-calendars, list-events, get-event, create-event, delete-event")
	calendarName := flag.String("calendar", "personal", "Calendar name (default: personal)")
	eventUID := flag.String("uid", "", "Event UID for get/delete-event operation")
	icalPath := flag.String("ical", "", "Path to iCalendar file for create-event operation")
	summary := flag.String("summary", "", "Event summary/title (for create-event)")
	startTime := flag.String("start", "", "Event start time (RFC3339 format, e.g., 2026-02-11T10:00:00Z)")
	endTime := flag.String("end", "", "Event end time (RFC3339 format, e.g., 2026-02-11T11:00:00Z)")

	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,
		CalDAV: fmt.Sprintf("%s/remote.php/dav/calendars/%s", strings.TrimSuffix(*serverURL, "/"), encodedUsername),
	}

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

	var err error
	switch *operation {
	case "list-calendars":
		err = client.listCalendars()
	case "list-events":
		err = client.listEvents(*calendarName)
	case "get-event":
		if *eventUID == "" {
			fmt.Println("Error: --uid is required for get-event operation")
			os.Exit(1)
		}
		err = client.getEvent(*calendarName, *eventUID)
	case "create-event":
		event := &Event{
			Summary: *summary,
		}
		if *icalPath != "" {
			icalContent, readErr := os.ReadFile(*icalPath)
			if readErr != nil {
				fmt.Printf("Error reading iCalendar file: %v\n", readErr)
				os.Exit(1)
			}
			event.iCal = string(icalContent)
		} else {
			// Parse times if provided
			if *startTime != "" {
				startTimeParsed, parseErr := time.Parse(time.RFC3339, *startTime)
				if parseErr != nil {
					fmt.Printf("Error parsing start time: %v\n", parseErr)
					os.Exit(1)
				}
				event.Start = startTimeParsed
			}
			if *endTime != "" {
				endTimeParsed, parseErr := time.Parse(time.RFC3339, *endTime)
				if parseErr != nil {
					fmt.Printf("Error parsing end time: %v\n", parseErr)
					os.Exit(1)
				}
				event.End = endTimeParsed
			}
		}
		err = client.createEvent(*calendarName, event)
	case "delete-event":
		if *eventUID == "" {
			fmt.Println("Error: --uid is required for delete-event operation")
			os.Exit(1)
		}
		err = client.deleteEvent(*calendarName, *eventUID)
	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) listCalendars() error {
	// PROPFIND body
	body := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<d:propfind xmlns:d="DAV:">
  <d:prop>
    <d:displayname />
    <d:getetag />
  </d:prop>
</d:propfind>`)

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

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

	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 calendars
	fmt.Println("Calendars:")
	fmt.Println("----------")
	for _, response := range multiStatus.Responses {
		relPath := strings.TrimPrefix(response.Href, c.Config.CalDAV+"/")
		if relPath == "" || !strings.HasSuffix(relPath, "/") {
			continue
		}

		// Skip system folders (inbox, outbox, trashbin)
		if relPath == "inbox/" || relPath == "outbox/" || relPath == "trashbin/" {
			continue
		}

		var mergedProp Prop
		for _, propStat := range response.PropStats {
			if strings.Contains(propStat.Status, "200") {
				if propStat.Prop.DisplayName != "" {
					mergedProp.DisplayName = propStat.Prop.DisplayName
				}
				if propStat.Prop.GetETag != "" {
					mergedProp.GetETag = propStat.Prop.GetETag
				}
			}
		}

		calendarName := strings.TrimSuffix(relPath, "/")
		fmt.Printf("Name: %s\n", mergedProp.DisplayName)
		fmt.Printf("Path: %s\n", calendarName)
		fmt.Printf("ETag: %s\n", mergedProp.GetETag)
		fmt.Println()
	}

	return nil
}

func (c *Client) listEvents(calendarName string) error {
	calendarPath := fmt.Sprintf("%s/%s/", c.Config.CalDAV, calendarName)

	// PROPFIND body
	body := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<d:propfind xmlns:d="DAV:">
  <d:prop>
    <d:getetag />
  </d:prop>
</d:propfind>`)

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

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

	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 events
	fmt.Printf("Events in '%s' calendar:\n", calendarName)
	fmt.Println("-----------------")
	eventCount := 0
	for _, response := range multiStatus.Responses {
		relPath := strings.TrimPrefix(response.Href, calendarPath)
		if relPath == "" || strings.HasSuffix(relPath, "/") {
			continue
		}

		eventCount++
		var etag string
		for _, propStat := range response.PropStats {
			if strings.Contains(propStat.Status, "200") {
				etag = propStat.Prop.GetETag
			}
		}

		// Extract UID from .ics filename
		filename := relPath
		if idx := strings.LastIndex(relPath, "/"); idx != -1 {
			filename = relPath[idx+1:]
		}
		eventUID := strings.TrimSuffix(filename, ".ics")
		fmt.Printf("UID: %s\n", eventUID)
		fmt.Printf("ETag: %s\n", etag)
		fmt.Println()
	}

	if eventCount == 0 {
		fmt.Println("(No events found)")
	}

	return nil
}

func (c *Client) getEvent(calendarName, eventUID string) error {
	eventPath := fmt.Sprintf("%s/%s/%s.ics", c.Config.CalDAV, calendarName, eventUID)

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

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("Accept", "text/calendar; charset=utf-8")

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

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

	content, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("failed to read response: %v", err)
	}

	fmt.Printf("Event: %s\n", eventUID)
	fmt.Println("--------")
	fmt.Println(string(content))

	return nil
}

func (c *Client) createEvent(calendarName string, event *Event) error {
	calendarPath := fmt.Sprintf("%s/%s/", c.Config.CalDAV, calendarName)

	// Generate iCalendar if not provided
	icalContent := event.iCal
	if icalContent == "" {
		if event.Summary == "" {
			return fmt.Errorf("event summary is required")
		}

		// Generate a simple VEVENT
		uid := fmt.Sprintf("%d", time.Now().UnixNano())
		now := time.Now().UTC().Format("20060102T150405Z")
		
		var startTime, endTime string
		if !event.Start.IsZero() {
			startTime = event.Start.UTC().Format("20060102T150405Z")
		} else {
			// Default to now + 1 hour
			startTime = time.Now().UTC().Add(1 * time.Hour).Format("20060102T150405Z")
		}
		
		if !event.End.IsZero() {
			endTime = event.End.UTC().Format("20060102T150405Z")
		} else {
			// Default to start + 1 hour
			endTime = startTime
		}

		icalContent = fmt.Sprintf(`BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//WLTBAgent//NextCalendar//EN
BEGIN:VEVENT
UID:%s
DTSTAMP:%s
DTSTART:%s
DTEND:%s
SUMMARY:%s
END:VEVENT
END:VCALENDAR`, uid, now, startTime, endTime, event.Summary)
	}

	eventPath := fmt.Sprintf("%s%s.ics", calendarPath, fmt.Sprintf("%d", time.Now().UnixNano()))
	
	req, err := http.NewRequest("PUT", eventPath, strings.NewReader(icalContent))
	if err != nil {
		return fmt.Errorf("request failed: %v", err)
	}

	req.SetBasicAuth(c.Config.User, c.Config.Token)
	req.Header.Set("Content-Type", "text/calendar; charset=utf-8")

	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("create event failed with status: %d", resp.StatusCode)
	}

	fmt.Printf("Event created successfully\n")

	return nil
}

func (c *Client) deleteEvent(calendarName, eventUID string) error {
	eventPath := fmt.Sprintf("%s/%s/%s.ics", c.Config.CalDAV, calendarName, eventUID)

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

	req.SetBasicAuth(c.Config.User, c.Config.Token)

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

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

	fmt.Printf("Event deleted: %s\n", eventUID)

	return nil
}
