package sitebuilder

import (
	"context"
	"errors"
	"io/fs"
	"lmika.dev/lmika/hugo-cms/models"
	"lmika.dev/lmika/hugo-cms/providers/bus"
	"lmika.dev/lmika/hugo-cms/providers/db"
	"lmika.dev/lmika/hugo-cms/providers/git"
	"lmika.dev/lmika/hugo-cms/providers/hugo"
	"lmika.dev/lmika/hugo-cms/providers/netlify"
	"lmika.dev/lmika/hugo-cms/providers/themes"
	"log"
	"os"
	"path/filepath"
)

type Service struct {
	db      *db.DB
	themes  *themes.Provider
	git     *git.Provider
	hugo    *hugo.Provider
	netlify *netlify.Provider
	bus     *bus.Bus
}

func New(
	db *db.DB,
	themes *themes.Provider,
	git *git.Provider,
	hugo *hugo.Provider,
	netlify *netlify.Provider,
	bus *bus.Bus,
) *Service {
	return &Service{
		db:      db,
		themes:  themes,
		git:     git,
		hugo:    hugo,
		netlify: netlify,
		bus:     bus,
	}
}

func (s *Service) CreateNewSite(site models.Site) models.Job {
	return models.Job{
		Do: func(ctx context.Context) error {
			s.signalSiteBuildingStarted(ctx, site)
			defer s.signalSiteBuildingFinished(ctx, site)

			return s.createSite(ctx, site)
		},
	}
}

func (s *Service) RebuildSite(oldSite, newSite models.Site) models.Job {
	return models.Job{
		Do: func(ctx context.Context) error {
			s.signalSiteBuildingStarted(ctx, newSite)
			defer s.signalSiteBuildingFinished(ctx, newSite)

			return s.rebuildSite(ctx, oldSite, newSite)
		},
	}
}

func (s *Service) RebuildSiteContent(oldSite, newSite models.Site) models.Job {
	return models.Job{
		Do: func(ctx context.Context) error {
			s.signalSiteBuildingStarted(ctx, newSite)
			defer s.signalSiteBuildingFinished(ctx, newSite)

			return s.rebuildContent(ctx, oldSite, newSite)
		},
	}
}

func (s *Service) rebuildSite(ctx context.Context, oldSite, newSite models.Site) error {
	// Teardown the existing site
	siteDir := s.hugo.SiteStagingDir(oldSite, hugo.BaseSiteDir)
	if err := os.RemoveAll(siteDir); err != nil {
		return err
	}

	if err := s.createSite(ctx, newSite); err != nil {
		return err
	}

	if err := s.writeAllContent(ctx, newSite); err != nil {
		return err
	}

	return s.publish(ctx, newSite)
}

func (s *Service) rebuildContent(ctx context.Context, oldSite, newSite models.Site) error {
	// Teardown the existing site
	siteDir := s.hugo.SiteStagingDir(oldSite, hugo.ContentSiteDir)
	if err := os.RemoveAll(siteDir); err != nil {
		return err
	}

	if err := s.writeAllContent(ctx, newSite); err != nil {
		return err
	}

	return s.publish(ctx, newSite)
}

func (s *Service) writeAllContent(ctx context.Context, newSite models.Site) error {
	if err := s.writeAllPosts(ctx, newSite); err != nil {
		return err
	}

	if err := s.writeAllPages(ctx, newSite); err != nil {
		return err
	}

	return nil
}

func (s *Service) fullRebuildNecessary(ctx context.Context, site models.Site) (bool, error) {
	dirsMustExists := []string{
		s.hugo.SiteStagingDir(site, hugo.BaseSiteDir),
		s.hugo.SiteStagingDir(site, hugo.ThemeSiteDir),
		s.hugo.SiteStagingDir(site, hugo.ContentSiteDir),
	}

	filesMustExists := []string{
		filepath.Join(s.hugo.SiteStagingDir(site, hugo.BaseSiteDir), "hugo.yaml"),
	}

	for _, dir := range dirsMustExists {
		if stat, err := os.Stat(dir); err != nil {
			if errors.Is(err, os.ErrNotExist) {
				return true, nil
			} else {
				return false, err
			}
		} else if !stat.IsDir() {
			return true, nil
		}
	}

	for _, file := range filesMustExists {
		if _, err := os.Stat(file); err != nil {
			if errors.Is(err, os.ErrNotExist) {
				return true, nil
			} else {
				return false, err
			}
		}
	}
	return false, nil
}

func (s *Service) createSite(ctx context.Context, site models.Site) error {
	themeMeta, ok := s.themes.Lookup(site.Theme)
	if !ok {
		return errors.New("theme not found")
	}

	// Build the site
	log.Printf(" .. build")
	if err := s.hugo.NewSite(ctx, site); err != nil {
		return err
	}

	// Setup the theme
	log.Printf(" .. theme")
	stagingDir := s.hugo.SiteStagingDir(site, hugo.ThemeSiteDir)
	if err := s.git.Clone(ctx, themeMeta.URL, stagingDir); err != nil {
		return err
	}

	if overlayFS := themeMeta.OverlayFS; overlayFS != nil && len(themeMeta.Overlays) > 0 {
		for src, target := range themeMeta.Overlays {
			srcFile, err := fs.ReadFile(overlayFS, src)
			if err != nil {
				return err
			}

			targetPath := filepath.Join(s.hugo.SiteStagingDir(site, hugo.BaseSiteDir), target)
			targetDir := filepath.Dir(targetPath)
			if err := os.MkdirAll(targetDir, 0755); err != nil {
				return err
			}

			if err := os.WriteFile(targetPath, srcFile, 0644); err != nil {
				return err
			}

			log.Printf(" .. overlay %v", targetPath)
		}
	}

	if err := s.hugo.ReconfigureSite(ctx, false, "hugo", site, themeMeta); err != nil {
		return err
	}

	if err := s.hugo.ReconfigureSite(ctx, true, "hugoPreview", site, themeMeta); err != nil {
		return err
	}

	return nil
}

func (s *Service) signalSiteBuildingStarted(ctx context.Context, site models.Site) {
	s.bus.Fire(models.Event{Type: models.EventSiteBuildingStart, Data: site})
}

func (s *Service) signalSiteBuildingFinished(ctx context.Context, site models.Site) {
	s.bus.Fire(models.Event{Type: models.EventSiteBuildingDone, Data: site})
}