package linkmeta

import (
	"errors"
	"io"
	"net/http"
	"strings"
	"time"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

var (
	Client = &http.Client{
		Timeout: 15 * time.Second,
	}

	ErrContentType = errors.New("Unsupported Content-Type")
)

type Meta struct {
	URL         string `json:"URL"`
	SiteName    string `json:"siteName,omitempty"`
	Color       string `json:"color,omitempty"`
	Title       string `json:"title"`
	Description string `json:"description"`
	ImageURL    string `json:"imageURL,omitempty"`
	VideoURL    string `json:"videoURL,omitempty"`
}

func Fetch(url string) (*Meta, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// TODO: Image links
	if !strings.HasPrefix(resp.Header.Get("Content-Type"), "text/html") {
		return nil, ErrContentType
	}

	return ExtractMeta(resp.Body, url)
}

func ExtractMeta(body io.Reader, url string) (*Meta, error) {
	meta := Meta{URL: url}
	var currentNode atom.Atom

	z := html.NewTokenizer(body)
	for {
		tt := z.Next()
		switch tt {
		case html.ErrorToken:
			if z.Err() == io.EOF {
				return &meta, nil
			}
			return nil, z.Err()

		case html.TextToken:
			if currentNode == atom.Title && meta.Title == "" {
				meta.Title = string(z.Text())
			}

		case html.StartTagToken, html.SelfClosingTagToken, html.EndTagToken:
			name, hasAttr := z.TagName()
			node := atom.Lookup(name)

			if node == atom.Meta && hasAttr {
				var key, val []byte
				var name, content string
				for hasAttr {
					key, val, hasAttr = z.TagAttr()
					switch atom.String(key) {
					case "name":
						name = string(val)

					case "property":
						name = string(val)

					case "content":
						content = string(val)
					}
				}

				if content != "" {
					switch name {
					case "og:site_name":
						meta.SiteName = content

					case "theme-color", "msapplication-TileColor":
						meta.Color = content

					case "og:title", "twitter:title", "title":
						meta.Title = content

					case "og:description", "twitter:description":
						meta.Description = content

					case "description":
						if meta.Description == "" {
							meta.Description = content
						}

					case "og:image", "og:image:secure_url", "twitter:image":
						if !strings.HasPrefix(meta.ImageURL, "https:") {
							meta.ImageURL = content
						}

					case "og:video:url", "og:video:secure_url", "twitter:player":
						if !strings.HasPrefix(meta.VideoURL, "https:") {
							meta.VideoURL = content
						}
					}
				}

				continue
			}

			if tt == html.StartTagToken {
				currentNode = node
			} else {
				currentNode = 0
			}

			if (node == atom.Head && tt == html.EndTagToken) || node == atom.Body {
				return &meta, nil
			}
		}
	}
}
