package handlers

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"sync"
	"time"

	"mgit.msbls.de/m/paliad/internal/branding"
)

const (
	giteaBaseURL  = "https://mgit.msbls.de"
	checkInterval = 5 * time.Minute
)

type fileEntry struct {
	RawURL       string
	DownloadName string
	ContentType  string
	RepoOwner    string
	RepoName     string
	FilePath     string
}

// fileRegistry maps the public download slug to the upstream Gitea object.
//
// RawURL / FilePath reference the actual file in mWorkRepo and must match the
// blob's name there exactly; renaming would 404 the proxy. DownloadName is
// what the browser saves the file as — that's a branding surface, so it
// renders branding.Name instead of the upstream filename.
//
// The URL slug ("hl-patents-style.dotm") is preserved as a stable public
// identifier so existing bookmarks keep working post-rebrand.
var fileRegistry = map[string]fileEntry{
	"hl-patents-style.dotm": {
		RawURL:       "https://mgit.msbls.de/m/mWorkRepo/raw/branch/main/6%20-%20material/Templates/Word/HL%20Patents%20Style.dotm",
		DownloadName: branding.Name + " Patents Style.dotm",
		ContentType:  "application/vnd.ms-word.template.macroEnabled.12",
		RepoOwner:    "m",
		RepoName:     "mWorkRepo",
		FilePath:     "6 - material/Templates/Word/HL Patents Style.dotm",
	},
}

type cacheEntry struct {
	mu          sync.RWMutex
	data        []byte
	sha         string
	lastChecked time.Time
	checking    bool
}

var (
	giteaToken  string
	fileCache   = make(map[string]*cacheEntry)
	fileCacheMu sync.Mutex
	httpClient  = &http.Client{Timeout: 30 * time.Second}
)

func getCacheEntry(name string) *cacheEntry {
	fileCacheMu.Lock()
	defer fileCacheMu.Unlock()
	ce, ok := fileCache[name]
	if !ok {
		ce = &cacheEntry{}
		fileCache[name] = ce
	}
	return ce
}

func handleFileDownload(w http.ResponseWriter, r *http.Request) {
	filename := r.PathValue("filename")
	entry, ok := fileRegistry[filename]
	if !ok {
		http.NotFound(w, r)
		return
	}

	ce := getCacheEntry(filename)

	ce.mu.RLock()
	hasData := len(ce.data) > 0
	needsCheck := time.Since(ce.lastChecked) >= checkInterval
	ce.mu.RUnlock()

	if !hasData {
		if err := fileFetch(ce, entry); err != nil {
			log.Printf("file proxy: fetch %s failed: %v", filename, err)
			http.Error(w, "Failed to fetch file", http.StatusBadGateway)
			return
		}
	} else if needsCheck {
		go fileCheckAndRefresh(ce, entry)
	}

	ce.mu.RLock()
	defer ce.mu.RUnlock()

	w.Header().Set("Content-Type", entry.ContentType)
	w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, entry.DownloadName))
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(ce.data)))
	w.Write(ce.data)
}

func handleFileRefresh(w http.ResponseWriter, r *http.Request) {
	fileCacheMu.Lock()
	for name := range fileCache {
		fileCache[name] = &cacheEntry{}
	}
	fileCacheMu.Unlock()

	writeJSON(w, http.StatusOK, map[string]string{"ok": "true", "message": "Cache cleared"})
}

// fileFetch downloads the file synchronously (first request).
func fileFetch(ce *cacheEntry, entry fileEntry) error {
	sha, _ := giteaLatestSHA(entry)

	data, err := giteaDownload(entry)
	if err != nil {
		return err
	}

	ce.mu.Lock()
	ce.data = data
	ce.sha = sha
	ce.lastChecked = time.Now()
	ce.mu.Unlock()
	return nil
}

// fileCheckAndRefresh checks the latest commit SHA and re-downloads if changed.
func fileCheckAndRefresh(ce *cacheEntry, entry fileEntry) {
	ce.mu.Lock()
	if ce.checking {
		ce.mu.Unlock()
		return
	}
	ce.checking = true
	ce.mu.Unlock()

	defer func() {
		ce.mu.Lock()
		ce.checking = false
		ce.mu.Unlock()
	}()

	latestSHA, err := giteaLatestSHA(entry)
	if err != nil {
		log.Printf("file proxy: SHA check for %s failed: %v", entry.DownloadName, err)
		ce.mu.Lock()
		ce.lastChecked = time.Now()
		ce.mu.Unlock()
		return
	}

	ce.mu.RLock()
	unchanged := latestSHA == ce.sha && ce.sha != ""
	ce.mu.RUnlock()

	if unchanged {
		ce.mu.Lock()
		ce.lastChecked = time.Now()
		ce.mu.Unlock()
		return
	}

	data, err := giteaDownload(entry)
	if err != nil {
		log.Printf("file proxy: download %s failed: %v", entry.DownloadName, err)
		ce.mu.Lock()
		ce.lastChecked = time.Now()
		ce.mu.Unlock()
		return
	}

	ce.mu.Lock()
	ce.data = data
	ce.sha = latestSHA
	ce.lastChecked = time.Now()
	ce.mu.Unlock()

	log.Printf("file proxy: updated %s (SHA: %.8s)", entry.DownloadName, latestSHA)
}

// giteaLatestSHA returns the SHA of the latest commit that touched the file.
func giteaLatestSHA(entry fileEntry) (string, error) {
	apiURL := fmt.Sprintf("%s/api/v1/repos/%s/%s/commits?path=%s&limit=1&sha=main",
		giteaBaseURL, entry.RepoOwner, entry.RepoName, url.QueryEscape(entry.FilePath))

	req, err := http.NewRequest("GET", apiURL, nil)
	if err != nil {
		return "", err
	}
	if giteaToken != "" {
		req.Header.Set("Authorization", "token "+giteaToken)
	}

	resp, err := httpClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("gitea API returned %d", resp.StatusCode)
	}

	var commits []struct {
		SHA string `json:"sha"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&commits); err != nil {
		return "", err
	}
	if len(commits) == 0 {
		return "", fmt.Errorf("no commits for path %s", entry.FilePath)
	}

	return commits[0].SHA, nil
}

// giteaDownload fetches the raw file content from Gitea.
func giteaDownload(entry fileEntry) ([]byte, error) {
	req, err := http.NewRequest("GET", entry.RawURL, nil)
	if err != nil {
		return nil, err
	}
	if giteaToken != "" {
		req.Header.Set("Authorization", "token "+giteaToken)
	}

	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("gitea raw returned %d", resp.StatusCode)
	}

	return io.ReadAll(resp.Body)
}
