package extra

import (
	"bytes"
	"strings"
	"fmt"
	"context"
	"io"
	"net/http"

	"nxcaldav/internal/backend"
	"regexp"
	"time"
)

type responseWriter struct {
	http.ResponseWriter
	buffer *bytes.Buffer
	status int
}

func (rw *responseWriter) Write(b []byte) (int, error) {
	return rw.buffer.Write(b)
}

func (rw *responseWriter) WriteHeader(status int) {
	rw.status = status
}

// Add Color To Calendar Propfind
func InjectColor(r *http.Request, ctx context.Context, handler http.Handler, w http.ResponseWriter, be *backend.DBBackend) {
	reqBody, _ := io.ReadAll(r.Body)
	r.Body = io.NopCloser(bytes.NewBuffer(reqBody))

	buf := &bytes.Buffer{}
	rw := &responseWriter{w, buf, http.StatusOK}
	handler.ServeHTTP(rw, r.WithContext(ctx))

	body := buf.Bytes()
	
	// this models after the Radicale Response, largely AI code
	// 1. Add namespaces to the root multistatus tag
	reMultistatus := regexp.MustCompile(`(<[a-zA-Z0-9]*:?multistatus)`)
	body = reMultistatus.ReplaceAll(body, []byte(`$1 xmlns:ICAL="http://apple.com/ns/ical/" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/"`))

	// 2. Response processing
	reResponse :=  regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?response.*?>.*?</[a-zA-Z0-9]*:?response>`)
	reHref :=      regexp.MustCompile(`<[a-zA-Z0-9]*:?href.*?>(.*?)</[a-zA-Z0-9]*:?href>`)
	rePropstat :=  regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?propstat.*?>.*?</[a-zA-Z0-9]*:?propstat>`)
	reStatusOk :=  regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?status.*?>HTTP/1.1 200 OK</[a-zA-Z0-9]*:?status>`)
	reProp :=      regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?prop.*?>.*?</[a-zA-Z0-9]*:?prop>`)
	rePropClose := regexp.MustCompile(`</[a-zA-Z0-9]*:?prop>`)

	body = reResponse.ReplaceAllFunc(body, func(resp []byte) []byte {
		hrefMatch := reHref.FindSubmatch(resp)
		if len(hrefMatch) < 2 {
			return resp
		}
		href := string(hrefMatch[1])
		color := be.GetColor(r.Context(), href)
		if color == "" {
			return resp
		}

		// 1. Strip any existing conflicting tags that might be in 404 blocks
		reStrip := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order).*?/>|<[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order).*?>.*?</[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order)>`)
		resp = reStrip.ReplaceAll(resp, []byte(""))

		fullColor := strings.ToLower(color)
		if len(fullColor) == 7 && strings.HasPrefix(fullColor, "#") {
			fullColor += "ff"
		}
		// Prepare the properties to inject
		props := fmt.Sprintf("<ICAL:calendar-color>%s</ICAL:calendar-color>", fullColor)
		props += fmt.Sprintf("<C:calendar-color>%s</C:calendar-color>", fullColor)
		props += "<ICAL:calendar-order>0</ICAL:calendar-order>"
		props += fmt.Sprintf("<CS:getctag>\"%d\"</CS:getctag>", time.Now().Unix())

		// 2. Try to inject into an existing 200 OK propstat
		has200 := false
		resp = rePropstat.ReplaceAllFunc(resp, func(ps []byte) []byte {
			if reStatusOk.Match(ps) {
				has200 = true
				return reProp.ReplaceAllFunc(ps, func(prop []byte) []byte {
					return rePropClose.ReplaceAllFunc(prop, func(closeTag []byte) []byte {
						return append([]byte(props), closeTag...)
					})
				})
			}
			return ps
		})

		// 3. If no 200 OK propstat was found, create one
		if !has200 {
			newPropstat := fmt.Sprintf("<propstat xmlns=\"DAV:\"><prop>%s</prop><status>HTTP/1.1 200 OK</status></propstat>", props)
			reResponseClose := regexp.MustCompile(`</[a-zA-Z0-9]*:?response>`)
			resp = reResponseClose.ReplaceAllFunc(resp, func(closeTag []byte) []byte {
				return append([]byte(newPropstat), closeTag...)
			})
		}

		return resp
	})

	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
	w.WriteHeader(rw.status)
	w.Write(body)
}

// modify caledar probfind
//
// this again modeled after the Radicale response
// Largly AI generated agian
func HandleDiscoveryPropfind(r *http.Request, ctx context.Context, handler http.Handler, w http.ResponseWriter, be *backend.DBBackend) {
	reqBody, _ := io.ReadAll(r.Body)
	r.Body = io.NopCloser(bytes.NewBuffer(reqBody))

	buf := &bytes.Buffer{}
	rw := &responseWriter{w, buf, http.StatusOK}
	handler.ServeHTTP(rw, r.WithContext(ctx))

	body := buf.Bytes()

	// 1. Unconditionally add namespaces to root tag
	reMultistatus := regexp.MustCompile(`(<[a-zA-Z0-9]*:?multistatus)`)
	body = reMultistatus.ReplaceAll(body, []byte(`$1 xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:card="urn:ietf:params:xml:ns:carddav"`))

	// 2. Response processing
	reResponse := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?response.*?>.*?</[a-zA-Z0-9]*:?response>`)
	reHref := regexp.MustCompile(`<[a-zA-Z0-9]*:?href.*?>(.*?)</[a-zA-Z0-9]*:?href>`)
	rePropstat := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?propstat.*?>.*?</[a-zA-Z0-9]*:?propstat>`)
	reStatusOk := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?status.*?>HTTP/1.1 200 OK</[a-zA-Z0-9]*:?status>`)
	reProp := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?prop.*?>.*?</[a-zA-Z0-9]*:?prop>`)
	rePropClose := regexp.MustCompile(`</[a-zA-Z0-9]*:?prop>`)

	body = reResponse.ReplaceAllFunc(body, func(resp []byte) []byte {
		hrefMatch := reHref.FindSubmatch(resp)
		if len(hrefMatch) < 2 {
			return resp
		}

		calHome, _ := be.CalendarHomeSetPath(ctx)
		cardHome, _ := be.AddressBookHomeSetPath(ctx)

		// Strip these tags ONLY from non-200 propstats
		resp = rePropstat.ReplaceAllFunc(resp, func(ps []byte) []byte {
			if !reStatusOk.Match(ps) {
				reTags := regexp.MustCompile(`(?s)<[a-zA-Z0-9:]*(calendar-home-set|addressbook-home-set).*?/>|<[a-zA-Z0-9:]*(calendar-home-set|addressbook-home-set).*?>.*?</[a-zA-Z0-9:]*(calendar-home-set|addressbook-home-set)>`)
				return reTags.ReplaceAll(ps, []byte(""))
			}
			return ps
		})

		props := ""
		if calHome != "" && !strings.Contains(string(resp), "calendar-home-set") {
			props += fmt.Sprintf("<C:calendar-home-set><href xmlns=\"DAV:\">%s</href></C:calendar-home-set>", calHome)
		}
		if cardHome != "" && !strings.Contains(string(resp), "addressbook-home-set") {
			props += fmt.Sprintf("<card:addressbook-home-set><href xmlns=\"DAV:\">%s</href></card:addressbook-home-set>", cardHome)
		}

		if props == "" {
			return resp
		}

		// 2. Try to inject into an existing 200 OK propstat
		has200 := false
		resp = rePropstat.ReplaceAllFunc(resp, func(ps []byte) []byte {
			if reStatusOk.Match(ps) {
				has200 = true
				return reProp.ReplaceAllFunc(ps, func(prop []byte) []byte {
					return rePropClose.ReplaceAllFunc(prop, func(closeTag []byte) []byte {
						return append([]byte(props), closeTag...)
					})
				})
			}
			return ps
		})

		// 3. If no 200 OK propstat was found, create one
		if !has200 {
			newPropstat := fmt.Sprintf("<propstat xmlns=\"DAV:\"><prop>%s</prop><status>HTTP/1.1 200 OK</status></propstat>", props)
			reResponseClose := regexp.MustCompile(`</[a-zA-Z0-9]*:?response>`)
			resp = reResponseClose.ReplaceAllFunc(resp, func(closeTag []byte) []byte {
				return append([]byte(newPropstat), closeTag...)
			})
		}
		return resp
	})

	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
	w.WriteHeader(rw.status)
	w.Write(body)
}
