// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package artifactcache

import (
	"fmt"
	"net"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/go-chi/render"
	"xorm.io/xorm"
)

func responseJson(w http.ResponseWriter, r *http.Request, code int, v ...any) {
	render.Status(r, code)
	if len(v) == 0 || v[0] == nil {
		render.JSON(w, r, struct{}{})
	} else if err, ok := v[0].(error); ok {
		logger.Errorf("%v %v: %v", r.Method, r.RequestURI, err)
		render.JSON(w, r, map[string]any{
			"error": err.Error(),
		})
	} else {
		render.JSON(w, r, v[0])
	}
}

func parseContentRange(s string) (int64, int64, error) {
	// support the format like "bytes 11-22/*" only
	s, _, _ = strings.Cut(strings.TrimPrefix(s, "bytes "), "/")
	s1, s2, _ := strings.Cut(s, "-")

	start, err := strconv.ParseInt(s1, 10, 64)
	if err != nil {
		return 0, 0, fmt.Errorf("parse %q: %w", s, err)
	}
	stop, err := strconv.ParseInt(s2, 10, 64)
	if err != nil {
		return 0, 0, fmt.Errorf("parse %q: %w", s, err)
	}
	return start, stop, nil
}

func getOutboundIP() (net.IP, error) {
	// FIXME: It makes more sense to use the gateway IP address of container network
	if conn, err := net.Dial("udp", "8.8.8.8:80"); err == nil {
		defer conn.Close()
		return conn.LocalAddr().(*net.UDPAddr).IP, nil
	}
	if ifaces, err := net.Interfaces(); err == nil {
		for _, i := range ifaces {
			if addrs, err := i.Addrs(); err == nil {
				for _, addr := range addrs {
					var ip net.IP
					switch v := addr.(type) {
					case *net.IPNet:
						ip = v.IP
					case *net.IPAddr:
						ip = v.IP
					}
					if ip.IsGlobalUnicast() {
						return ip, nil
					}
				}
			}
		}
	}
	return nil, fmt.Errorf("no outbound IP address found")
}

// engine is a wrapper of *xorm.Engine, with a lock.
// To avoid racing of sqlite, we don't care performance here.
type engine struct {
	e *xorm.Engine
	m sync.Mutex
}

func (e *engine) Exec(f func(*xorm.Session) error) error {
	e.m.Lock()
	defer e.m.Unlock()

	sess := e.e.NewSession()
	defer sess.Close()

	return f(sess)
}

func (e *engine) ExecBool(f func(*xorm.Session) (bool, error)) (bool, error) {
	e.m.Lock()
	defer e.m.Unlock()

	sess := e.e.NewSession()
	defer sess.Close()

	return f(sess)
}