package service

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

func (a *App) artifactRepo(node string) string {
	return fmt.Sprintf("%s/%s/%s", strings.TrimRight(a.settings.HarborRegistry, "/"), strings.Trim(a.settings.HarborProject, "/"), node)
}

func (a *App) ensureHarborProject() error {
	if strings.TrimSpace(a.settings.HarborAPIBase) == "" || strings.TrimSpace(a.settings.HarborPassword) == "" {
		return fmt.Errorf("harbor admin credentials are not configured")
	}
	client := &http.Client{Timeout: 30 * time.Second}
	project := strings.TrimSpace(a.settings.HarborProject)
	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/projects?name=%s", strings.TrimRight(a.settings.HarborAPIBase, "/"), url.QueryEscape(project)), nil)
	if err != nil {
		return err
	}
	req.SetBasicAuth(strings.TrimSpace(a.settings.HarborUsername), strings.TrimSpace(a.settings.HarborPassword))
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 300 {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return fmt.Errorf("harbor project lookup failed: %s: %s", resp.Status, strings.TrimSpace(string(body)))
	}
	var projects []struct {
		Name string `json:"name"`
	}
	if err := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&projects); err != nil {
		return err
	}
	for _, item := range projects {
		if strings.EqualFold(strings.TrimSpace(item.Name), project) {
			return nil
		}
	}
	payload := map[string]any{
		"project_name": project,
		"metadata":     map[string]string{"public": "false"},
	}
	data, err := json.Marshal(payload)
	if err != nil {
		return err
	}
	req, err = http.NewRequest(http.MethodPost, fmt.Sprintf("%s/projects", strings.TrimRight(a.settings.HarborAPIBase, "/")), bytes.NewReader(data))
	if err != nil {
		return err
	}
	req.SetBasicAuth(strings.TrimSpace(a.settings.HarborUsername), strings.TrimSpace(a.settings.HarborPassword))
	req.Header.Set("Content-Type", "application/json")
	resp, err = client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict {
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
	return fmt.Errorf("harbor project create failed: %s: %s", resp.Status, strings.TrimSpace(string(body)))
}

func (a *App) pruneHarborArtifacts(node string, keep int) error {
	client := &http.Client{Timeout: 30 * time.Second}
	repo := url.PathEscape(node)
	apiBase := strings.TrimRight(a.settings.HarborAPIBase, "/")
	project := url.PathEscape(strings.TrimSpace(a.settings.HarborProject))
	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/projects/%s/repositories/%s/artifacts?page_size=100&with_tag=true", apiBase, project, repo), nil)
	if err != nil {
		return err
	}
	req.SetBasicAuth(strings.TrimSpace(a.settings.HarborUsername), strings.TrimSpace(a.settings.HarborPassword))
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusNotFound {
		return nil
	}
	if resp.StatusCode >= 300 {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return fmt.Errorf("harbor artifact list failed: %s: %s", resp.Status, strings.TrimSpace(string(body)))
	}
	var artifacts []struct {
		Digest   string `json:"digest"`
		PushTime string `json:"push_time"`
		Tags     []struct {
			Name string `json:"name"`
		} `json:"tags"`
	}
	if err := json.NewDecoder(io.LimitReader(resp.Body, 2<<20)).Decode(&artifacts); err != nil {
		return err
	}
	sort.Slice(artifacts, func(i, j int) bool {
		return artifacts[i].PushTime > artifacts[j].PushTime
	})
	for idx, artifact := range artifacts {
		if idx < keep {
			continue
		}
		ref := url.PathEscape(artifact.Digest)
		req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/projects/%s/repositories/%s/artifacts/%s", apiBase, project, repo, ref), nil)
		if err != nil {
			return err
		}
		req.SetBasicAuth(strings.TrimSpace(a.settings.HarborUsername), strings.TrimSpace(a.settings.HarborPassword))
		resp, err := client.Do(req)
		if err != nil {
			return err
		}
		resp.Body.Close()
		if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusNotFound {
			return fmt.Errorf("harbor artifact delete failed for %s: %s", artifact.Digest, resp.Status)
		}
	}
	return nil
}
