package twt_avro

import (
	"io"
	"time"

	"github.com/hamba/avro"
	"go.yarn.social/lextwt"
	"go.yarn.social/types"
)

const (
	keySubject     = "social.yarn.lextwt.subject"
	keyCode        = "social.yarn.lextwt.code"
	keyMention     = "social.yarn.lextwt.mention"
	keyHashtag     = "social.yarn.lextwt.hashtag"
	keyLink        = "social.yarn.lextwt.link"
	keyBangmention = "social.yarn.lextwt.bangmention"
	keyLinesep     = "social.yarn.lextwt.linesep"
	keyComment     = "social.yarn.lextwt.comment"
	keyText        = "social.yarn.lextwt.text"
	keyTwt         = "social.yarn.lextwt.twt"
	keyTwter       = "social.yarn.lextwt.twter"
	keyRegistry    = "social.yarn.lextwt.registry"
)

func Register() {
	avro.Register(keyText, "")
	avro.Register(keySubject, Subject{})
	avro.Register(keyCode, Code{})
	avro.Register(keyMention, Mention{})
	avro.Register(keyHashtag, Hashtag{})
	avro.Register(keyLink, Link{})
	avro.Register(keyBangmention, Bangmention{})
	avro.Register(keyLinesep, Linesep{})
	avro.Register(keyComment, Comment{})
	avro.Register(keyTwt, Twt{})
	avro.Register(keyTwter, Twter{})
	avro.Register(keyRegistry, Registry{})
}

func Elem(o any) any {
	key := "unknown"
	switch o.(type) {
	case Subject:
		key = keySubject
	case Code:
		key = keyCode
	case Mention:
		key = keyMention
	case Hashtag:
		key = keyHashtag
	case Link:
		key = keyLink
	case Bangmention:
		key = keyBangmention
	case Linesep:
		key = keyLinesep
	case Comment:
		key = keyComment
	case string:
		return o
	}

	return map[string]any{key: o}
}

func Msg(items ...any) []any {
	return items
}

func FromTwt(twt types.Twt) Twt {
	return FromLextwt(twt.(*lextwt.Twt))
}

func FromLextwt(twt *lextwt.Twt) Twt {
	if twt == nil {
		return Twt{}
	}

	ts := twt.Created()
	_, offset := ts.Zone()
	l := Twt{
		Nick:          twt.Twter().Nick,
		URI:           twt.Twter().URI,
		Created:       ts.UnixMilli(),
		CreatedOffset: offset,
		CreatedZone:   "",
	}

	for _, e := range twt.Elems() {
		if e == nil {
			continue
		}

		if e == lextwt.LineSeparator {
			l.Msg = append(l.Msg, Elem(Linesep{}))
			continue
		}

		switch e := e.(type) {
		case *lextwt.Subject:
			l.Msg = append(l.Msg, Elem(Subject{Subject: e.Subject(), Tag: e.Tag().Text(), Target: e.Tag().Target()}))
		case *lextwt.Code:
			l.Msg = append(l.Msg, Elem(Code{Code: e.Text(), Codetype: int(e.CodeType())}))
		case *lextwt.Mention:
			l.Msg = append(l.Msg, Elem(Mention{Name: e.Name(), Domain: e.Domain(), Target: e.Target()}))
		case *lextwt.Tag:
			l.Msg = append(l.Msg, Elem(Hashtag{Tag: e.Text(), Target: e.Target()}))
		case *lextwt.Link:
			l.Msg = append(l.Msg, Elem(Link{LinkType: int(e.LinkType()), Text: e.Text(), Target: e.Target(), Title: e.Title()}))
		case *lextwt.BangMention:
			l.Msg = append(l.Msg, Elem(Bangmention{Name: e.Name(), Target: e.Target()}))
		case *lextwt.Comment:
			l.Msg = append(l.Msg, Elem(Comment{Comment: e.Text(), Key: e.Key(), Value: e.Value()}))
		case *lextwt.Text:
			l.Msg = append(l.Msg, Elem(e.Literal()))
		}
	}

	return l
}

func (l Twt) ToTwt() types.Twt {
	return l.ToLextwt()
}

func (lx Twt) ToLextwt() *lextwt.Twt {
	twter := types.Twter{
		Nick: lx.Nick,
		URI:  lx.URI,
	}

	ts := time.UnixMilli(lx.Created)

	if tz := time.FixedZone(lx.CreatedZone, lx.CreatedOffset); tz != nil {
		ts = ts.In(tz)
	}
	dt := lextwt.NewDateTime(ts, ts.Format(time.RFC3339))

	elems := make([]lextwt.Elem, 0, len(lx.Msg))

	for _, e := range lx.Msg {

		switch e := e.(type) {
		case map[string]any:
			if text, ok := e["string"].(string); ok {
				elems = append(elems, lextwt.NewText(text))
			}

			if e, ok := e[keySubject].(map[string]any); ok {
				subject := read[string](e, "subject")
				tag := read[string](e, "tag")
				target := read[string](e, "target")

				if subject == "" {
					elems = append(elems, lextwt.NewSubjectTag(tag, target))
				} else {
					elems = append(elems, lextwt.NewSubject(subject))
				}
			}

			if m, ok := e[keyCode].(map[string]any); ok {
				code := read[string](m, "code")
				codeType := lextwt.CodeType(read[int8](m, "codetype"))
				elems = append(elems, lextwt.NewCode(code, codeType))
			}

			if e, ok := e[keyMention].(map[string]any); ok {
				name := read[string](e, "name")
				target := read[string](e, "target")
				elems = append(elems, lextwt.NewMention(name, target))
			}

			if e, ok := e[keyHashtag].(map[string]any); ok {
				tag := read[string](e, "tag")
				target := read[string](e, "target")
				elems = append(elems, lextwt.NewTag(tag, target))
			}

			if e, ok := e[keyLink].(map[string]any); ok {
				text := read[string](e, "text")
				target := read[string](e, "target")
				linkType := lextwt.LinkType(read[int](e, "linkType"))
				elems = append(elems, lextwt.NewLink(text, target, linkType))
			}

			if e, ok := e[keyBangmention].(map[string]any); ok {
				name := read[string](e, "name")
				target := read[string](e, "target")
				elems = append(elems, lextwt.NewBangMention(name, target))
			}

			if e, ok := e[keyComment].(map[string]any); ok {
				comment := read[string](e, "comment")
				key := read[string](e, "key")
				value := read[string](e, "value")

				if key != "" {
					elems = append(elems, lextwt.NewCommentValue(comment, key, value))
				} else {
					elems = append(elems, lextwt.NewComment(comment))
				}
			}

			if _, ok := e[keyLinesep].(map[string]any); ok {
				elems = append(elems, lextwt.LineSeparator)
			}
		}
	}

	return lextwt.NewTwt(twter, dt, elems...)
}

func read[T any](m map[string]any, k string) T {
	val, ok := m[k].(T)
	if !ok {
		var zero T
		return zero
	}
	return val
}

type TwtRegistry interface {
	Twters() []*types.Twter
	Preamble() lextwt.Comments
	Twts() types.Twts
	WriteTo(w io.Writer) (int64, error)
}

func EncodeRegistry(r TwtRegistry) ([]byte, error) {
	out := Registry{}

	for _, comment := range r.Preamble() {
		if comment.Key() != "" {
			out.Preamble = append(out.Preamble, Comment{Key: comment.Key(), Value: comment.Value()})
			continue
		} 
		out.Preamble = append(out.Preamble, Comment{Comment: comment.Text()})
	}

	for _, twter := range r.Twters() {
		out.Twters = append(out.Twters, Twter{Nick: twter.Nick, URI: twter.URI})
	}

	for _, twt := range r.Twts() {
		out.Twts = append(out.Twts, FromTwt(twt))
	}

	return out.Marshal()
}

func DecodeRegistry(b []byte) (TwtRegistry, error) {
	var out Registry
	if err := out.Unmarshal(b); err != nil {
		return nil, err
	}

	preamble := make(lextwt.Comments, len(out.Preamble))
	for _, comment := range out.Preamble {
		if comment.Key != "" {
			preamble = append(preamble, lextwt.NewCommentValue(comment.Comment, comment.Key, comment.Value))
		} else {
			preamble = append(preamble, lextwt.NewComment(comment.Comment))
		}
	}

	twters := make([]*types.Twter, len(out.Twters))
	for _, twter := range out.Twters {
		twters = append(twters, &types.Twter{Nick:twter.Nick, URI:twter.URI}) 
	}

	twts := make(types.Twts, len(out.Twts))
	for _, twt := range out.Twts {
		twts = append(twts, twt.ToTwt())
	}

	return lextwt.NewTwtRegistry(twters, preamble, twts), nil
}