package httputil

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/goccy/go-json"
	"github.com/tomnomnom/linkheader"
	"golang.org/x/exp/slices"
	"willnorris.com/go/microformats"

	"source.toby3d.me/toby3d/auth/internal/common"
	"source.toby3d.me/toby3d/auth/internal/domain"
)

const RelIndieauthMetadata = "indieauth-metadata"

var ErrEndpointNotExist = domain.NewError(
	domain.ErrorCodeServerError,
	"cannot found any endpoints",
	"https://indieauth.net/source/#discovery-0",
)

func ExtractFromMetadata(client *http.Client, u string) (*domain.Metadata, error) {
	req, err := http.NewRequest(http.MethodGet, u, nil)
	if err != nil {
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	buf := bytes.NewBuffer(body)

	endpoints := ExtractEndpoints(buf, resp.Request.URL, resp.Header.Get(common.HeaderLink), RelIndieauthMetadata)
	if len(endpoints) == 0 {
		return nil, ErrEndpointNotExist
	}

	if resp, err = client.Get(endpoints[len(endpoints)-1].String()); err != nil {
		return nil, fmt.Errorf("failed to fetch metadata endpoint configuration: %w", err)
	}

	result := new(domain.Metadata)
	if err = json.NewDecoder(resp.Body).Decode(result); err != nil {
		return nil, fmt.Errorf("cannot unmarshal emtadata configuration: %w", err)
	}

	return result, nil
}

func ExtractEndpoints(body io.Reader, u *url.URL, linkHeader, rel string) []*url.URL {
	results := make([]*url.URL, 0)

	urls, err := ExtractEndpointsFromHeader(linkHeader, rel)
	if err == nil {
		results = append(results, urls...)
	}

	urls, err = ExtractEndpointsFromBody(body, u, rel)
	if err == nil {
		results = append(results, urls...)
	}

	return results
}

func ExtractEndpointsFromHeader(linkHeader, rel string) ([]*url.URL, error) {
	results := make([]*url.URL, 0)

	for _, link := range linkheader.Parse(linkHeader) {
		if !strings.EqualFold(link.Rel, rel) {
			continue
		}

		u, err := url.Parse(link.URL)
		if err != nil {
			return nil, fmt.Errorf("cannot parse header endpoint: %w", err)
		}

		results = append(results, u)
	}

	return results, nil
}

func ExtractEndpointsFromBody(body io.Reader, u *url.URL, rel string) ([]*url.URL, error) {
	endpoints, ok := microformats.Parse(body, u).Rels[rel]
	if !ok || len(endpoints) == 0 {
		return nil, ErrEndpointNotExist
	}

	results := make([]*url.URL, 0)

	for i := range endpoints {
		u, err := url.Parse(endpoints[i])
		if err != nil {
			return nil, fmt.Errorf("cannot parse body endpoint: %w", err)
		}

		results = append(results, u)
	}

	return results, nil
}

func ExtractProperty(body io.Reader, u *url.URL, itemType, key string) []any {
	if data := microformats.Parse(body, u); data != nil {
		return FindProperty(data.Items, itemType, key)
	}

	return nil
}

func FindProperty(src []*microformats.Microformat, itemType, key string) []any {
	for _, item := range src {
		if slices.Contains(item.Type, itemType) {
			return item.Properties[key]
		}

		if result := FindProperty(item.Children, itemType, key); result != nil {
			return result
		}
	}

	return nil
}
