package atomconv

import (
	"bytes"
	"context"
	"html/template"
	"io"
	"mime"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"tildegit.org/tjp/sliderule/gemini"
	"tildegit.org/tjp/sliderule/gemini/gemtext"
	"tildegit.org/tjp/sliderule/internal/types"
)

// Convert turns a gemini document to Atom format.
//
// It identifies feed fields and entries according to the specification at
// gemini://gemini.circumlunar.space/docs/companion/subscription.gmi
func Convert(wr io.Writer, doc gemtext.Document, location *url.URL) error {
	if location == nil {
		panic("atomconv.Convert: provided location was nil")
	}

	if _, err := wr.Write([]byte(`<?xml version="1.0" encoding="utf-8"?>`)); err != nil {
		return err
	}
	if _, err := wr.Write([]byte{'\n'}); err != nil {
		return err
	}
	return atomTmpl.Execute(wr, parseGemSub(doc, location))
}

// Auto is a middleware which builds atom feeds for any gemtext pages.
//
// It looks for requests ending with the '.atom' extension, passes through the request
// with the extension clipped off, then if the response is in gemtext it converts it to
// an Atom feed according to the gmisub spec at
// gemini://gemini.circumlunar.space/docs/companion/subscription.gmi
var Auto = types.Middleware(func(h types.Handler) types.Handler {
	return types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response {
		if request.Scheme != "gemini" || !strings.HasSuffix(request.Path, ".atom") {
			return h.Handle(ctx, request)
		}

		r := *request
		u := *request.URL
		u.Path = u.Path[:len(u.Path)-5]
		r.URL = &u

		response := h.Handle(ctx, &r)
		if response.Status != gemini.StatusSuccess {
			return response
		}

		mtype, _, err := mime.ParseMediaType(response.Meta.(string))
		if err != nil || mtype != "text/gemini" {
			return response
		}

		defer func() {
			_ = response.Close()
		}()

		doc, err := gemtext.Parse(response.Body)
		if err != nil {
			return gemini.Failure(err)
		}

		buf := &bytes.Buffer{}
		if err := Convert(buf, doc, request.URL); err != nil {
			return gemini.Failure(err)
		}
		return gemini.Success("application/atom+xml; charset=utf-8", buf)
	})
})

type gmiSub struct {
	ID       template.URL
	Title    string
	Subtitle string
	Updated  string

	Entries []gmiSubEntry
}

type gmiSubEntry struct {
	ID      template.URL
	Updated string
	Title   string
}

var linkElemRE = regexp.MustCompile(`(\d{4})-([0-1]\d)-([0-3]\d)`)

func parseGemSub(doc gemtext.Document, location *url.URL) *gmiSub {
	sub := &gmiSub{ID: template.URL(location.String())}
	updated := time.Time{}

	for i, line := range doc {
		switch line.Type() {
		case gemtext.LineTypeHeading1:
			if sub.Title != "" {
				continue
			}

			sub.Title = line.(gemtext.HeadingLine).Body()

			for { // skip any empty lines
				i += 1
				if i >= len(doc) || strings.TrimPrefix(doc[i].String(), "\r") != "\n" {
					break
				}
			}
			if i < len(doc) && doc[i].Type() == gemtext.LineTypeHeading2 {
				sub.Subtitle = doc[i].(gemtext.HeadingLine).Body()
			}
		case gemtext.LineTypeLink:
			label := line.(gemtext.LinkLine).Label()
			if len(label) < 10 {
				continue
			}
			match := linkElemRE.FindStringSubmatch(label[:10])
			if match == nil {
				continue
			}

			year, err := strconv.Atoi(match[1])
			if err != nil {
				continue
			}
			month, err := strconv.Atoi(match[2])
			if err != nil || month > 12 {
				continue
			}
			day, err := strconv.Atoi(match[3])
			if err != nil || day > 31 {
				continue
			}

			entryUpdated := time.Date(year, time.Month(month), day, 12, 0, 0, 0, time.UTC)
			entryTitle := strings.TrimLeft(strings.TrimPrefix(strings.TrimLeft(label[10:], " \t"), "-"), " \t")

			sub.Entries = append(sub.Entries, gmiSubEntry{
				ID:      template.URL(line.(gemtext.LinkLine).URL()),
				Updated: entryUpdated.Format(time.RFC3339),
				Title:   entryTitle,
			})

			if entryUpdated.After(updated) {
				updated = entryUpdated
				sub.Updated = updated.Format(time.RFC3339)
			}
		}
	}

	return sub
}

var atomTmpl = template.Must(template.New("atom").Parse(`
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>{{.ID}}</id>
  <link href="{{.ID}}"/>
  <title>{{.Title}}</title>
  {{- if .Subtitle }}
  <subtitle>{{.Subtitle}}</subtitle>
  {{- end }}
  <updated>{{.Updated}}</updated>
{{- range .Entries }}
  <entry>
    <id>{{.ID}}</id>
    <link rel="alternate" href="{{.ID}}"/>
    <title>{{.Title}}</title>
    <updated>{{.Updated}}</updated>
  </entry>
{{- end }}
</feed>
`[1:]))