package web

import (
	"html/template"
	"math"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/charmbracelet/log/v2"
	"github.com/charmbracelet/soft-serve/pkg/access"
	"github.com/charmbracelet/soft-serve/pkg/backend"
	"github.com/charmbracelet/soft-serve/pkg/config"
	"github.com/charmbracelet/soft-serve/pkg/proto"
	"github.com/charmbracelet/soft-serve/pkg/ui/common"
	"github.com/dustin/go-humanize"
)

type HomeRepository struct {
	Name        string
	ProjectName string
	Description string
	IsPrivate   bool
	CloneURL    string
	UpdatedAt   string
}

type HomeData struct {
	BaseData
	PaginationData
	Repo         proto.Repository
	Repositories []HomeRepository
	ReadmeHTML   template.HTML
}

type repoItem struct {
	repo       proto.Repository
	lastUpdate *time.Time
}

func home(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	logger := log.FromContext(ctx)
	cfg := config.FromContext(ctx)
	be := backend.FromContext(ctx)

	if be == nil {
		logger.Debug("backend not found in context")
		renderInternalServerError(w, r)
		return
	}

	repos, err := be.Repositories(ctx)
	if err != nil {
		logger.Debug("failed to get repositories", "err", err)
		renderInternalServerError(w, r)
		return
	}

	var readmeHTML template.HTML
	homeRepos := make([]HomeRepository, 0)
	items := make([]repoItem, 0)

	readmeHTML, err = getServerReadme(ctx, be)
	if err != nil {
		logger.Debug("failed to get server README", "err", err)
	}

	for _, r := range repos {
		if r.IsHidden() {
			continue
		}

		al := be.AccessLevelByPublicKey(ctx, r.Name(), nil)
		if al >= access.ReadOnlyAccess {
			var lastUpdate *time.Time
			lu := r.UpdatedAt()
			if !lu.IsZero() {
				lastUpdate = &lu
			}
			items = append(items, repoItem{
				repo:       r,
				lastUpdate: lastUpdate,
			})
		}
	}

	sort.Slice(items, func(i, j int) bool {
		if items[i].lastUpdate == nil && items[j].lastUpdate != nil {
			return false
		}
		if items[i].lastUpdate != nil && items[j].lastUpdate == nil {
			return true
		}
		if items[i].lastUpdate == nil && items[j].lastUpdate == nil {
			return items[i].repo.Name() < items[j].repo.Name()
		}
		return items[i].lastUpdate.After(*items[j].lastUpdate)
	})

	page := 1
	if pageStr := r.URL.Query().Get("page"); pageStr != "" {
		if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
			page = p
		}
	}

	totalRepos := len(items)
	totalPages := int(math.Ceil(float64(totalRepos) / float64(defaultReposPerPage)))
	if totalPages < 1 {
		totalPages = 1
	}

	if page > totalPages {
		page = totalPages
	}
	if page < 1 {
		page = 1
	}

	startIdx := (page - 1) * defaultReposPerPage
	endIdx := startIdx + defaultReposPerPage
	if endIdx > totalRepos {
		endIdx = totalRepos
	}

	paginatedItems := items[startIdx:endIdx]

	for _, item := range paginatedItems {
		repo := item.repo
		name := repo.Name()
		projectName := repo.ProjectName()
		description := strings.TrimSpace(repo.Description())
		cloneURL := common.RepoURL(cfg.SSH.PublicURL, name)

		var updatedAt string
		if item.lastUpdate != nil {
			updatedAt = humanize.Time(*item.lastUpdate)
		}

		homeRepos = append(homeRepos, HomeRepository{
			Name:        name,
			ProjectName: projectName,
			Description: description,
			IsPrivate:   repo.IsPrivate(),
			CloneURL:    cloneURL,
			UpdatedAt:   updatedAt,
		})
	}

	data := HomeData{
		BaseData: BaseData{
			ServerName: cfg.Name,
			ActiveTab:  "repositories",
		},
		PaginationData: PaginationData{
			Page:        page,
			TotalPages:  totalPages,
			HasPrevPage: page > 1,
			HasNextPage: page < totalPages,
		},
		Repositories: homeRepos,
		ReadmeHTML:   readmeHTML,
	}

	renderHTML(w, "home.html", data)
}
