package posts

import (
	"bytes"
	"context"
	"github.com/PuerkitoBio/goquery"
	"github.com/Southclaws/fault"
	"html/template"
	"lmika.dev/lmika/hugo-cms/models"
	"lmika.dev/lmika/hugo-cms/providers/db"
	"lmika.dev/lmika/hugo-cms/services/jobs"
	"lmika.dev/lmika/hugo-cms/services/sitebuilder"
	"resty.dev/v3"
	"time"
)

type Service struct {
	db   *db.DB
	sb   *sitebuilder.Service
	jobs *jobs.Service
}

func New(
	db *db.DB,
	sb *sitebuilder.Service,
	jobs *jobs.Service,
) *Service {
	return &Service{
		db:   db,
		sb:   sb,
		jobs: jobs,
	}
}

func (s *Service) ListPostOfSite(ctx context.Context, site models.Site) ([]models.Post, error) {
	return s.db.ListPostsOfSite(ctx, site.ID)
}

func (s *Service) GetPost(ctx context.Context, id int) (models.Post, error) {
	post, err := s.db.GetPost(ctx, int64(id))
	if err != nil {
		return models.Post{}, err
	}

	return post, nil
}

func (s *Service) DeletePost(ctx context.Context, site models.Site, id int) error {
	post, err := s.db.GetPost(ctx, int64(id))
	if err != nil {
		return err
	}

	if err := s.db.DeletePost(ctx, int64(id)); err != nil {
		return err
	}

	return s.jobs.Queue(ctx, s.sb.DeletePost(site, post))
}

func (s *Service) Create(ctx context.Context, site models.Site, req NewPost) (models.Post, error) {
	post := models.Post{
		SiteID:      site.ID,
		Title:       req.Title,
		Body:        req.Body,
		State:       models.PostStatePublished,
		PublishDate: time.Now(),
	}

	if err := s.Save(ctx, site, &post); err != nil {
		return models.Post{}, err
	}

	return post, nil
}

func (s *Service) CreateLinkPost(ctx context.Context, site models.Site, req NewLinkPost) (models.Post, error) {
	fl, err := s.fetchLinkedPage(ctx, req)
	if err != nil {
		return models.Post{}, err
	}

	var bodyTemplate bytes.Buffer
	if err := linkedPostTemplate.Execute(&bodyTemplate, fl); err != nil {
		return models.Post{}, err
	}

	post := models.Post{
		SiteID: site.ID,
		Title:  fl.LinkTitle,
		Body:   bodyTemplate.String(),
	}

	return post, nil
}

func (s *Service) Save(ctx context.Context, site models.Site, post *models.Post) error {
	post.SiteID = site.ID

	if post.ID == 0 {
		post.CreatedAt = time.Now()
		post.UpdatedAt = time.Now()
		if err := s.db.InsertPost(ctx, post); err != nil {
			return err
		}
	} else {
		post.UpdatedAt = time.Now()
		if err := s.db.UpdatePost(ctx, post); err != nil {
			return err
		}
	}

	return s.jobs.Queue(ctx, s.sb.WritePost(site, *post))
}

func (s *Service) fetchLinkedPage(ctx context.Context, req NewLinkPost) (fl fetchedLink, err error) {
	client := resty.New()
	defer client.Close()

	// Fetch the linked site
	res, err := client.R().WithContext(ctx).Get(req.LinkURL)
	if err != nil {
		return fetchedLink{}, err
	} else if res.StatusCode() != 200 {
		return fetchedLink{}, fault.Newf("page returns non-200 status code %d", res.StatusCode())
	}

	if len(res.RedirectHistory()) > 0 {
		fl.LinkURL = res.RedirectHistory()[len(res.RedirectHistory())-1].URL
	} else {
		fl.LinkURL = req.LinkURL
	}

	resDom, err := goquery.NewDocumentFromReader(bytes.NewReader(res.Bytes()))
	if err != nil {
		return fetchedLink{}, err
	}

	fl.LinkTitle = resDom.Find("title").Text()

	// Fetch the via site
	if req.ViaURL != "" {
		if res, err := client.R().WithContext(ctx).Get(req.ViaURL); err == nil && res.StatusCode() == 200 {
			if len(res.RedirectHistory()) > 0 {
				fl.ViaURL = res.RedirectHistory()[len(res.RedirectHistory())-1].URL
			} else {
				fl.ViaURL = req.LinkURL
			}

			if viaDom, err := goquery.NewDocumentFromReader(bytes.NewReader(res.Bytes())); err == nil {
				fl.ViaTitle = viaDom.Find("title").Text()
			}
		}
	}
	return fl, nil
}

type NewPost struct {
	Title  string            `json:"title" form:"title"`
	Body   string            `json:"body" form:"body"`
	Params map[string]string `json:"params" form:"params"`
}

type NewLinkPost struct {
	LinkURL string `json:"title" form:"link_url"`
	ViaURL  string `json:"body" form:"via_url"`
}

type fetchedLink struct {
	LinkURL   string
	LinkTitle string
	ViaURL    string
	ViaTitle  string
}

var linkedPostTemplate = template.Must(template.New("").Parse(`🔗 [{{.LinkTitle}}]({{.LinkURL}})

{{if .ViaURL}}Via: [{{.ViaTitle}}]({{.ViaURL}}){{end}}`))