package flaresolverr

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"github.com/Danny-Dasilva/CycleTLS/cycletls"
)

var instances map[string]*Flaresolverr = make(map[string]*Flaresolverr)

type Flaresolverr struct {
	url       string
	v1Url     string
	sessionID string
	cycletlsC *cycletlsClient
	proxy     string
}

func GetInstance(URL string, sessionID string, proxy string) (*Flaresolverr, error) {
	if instance, ok := instances[URL]; ok {
		return instance, nil
	}
	flareSolverr := &Flaresolverr{
		url:       URL,
		v1Url:     strings.TrimSuffix(URL, "/") + "/v1",
		sessionID: sessionID,
		proxy:     proxy,
	}
	ua, ja3, err := flareSolverr.GetJa3AndUserAgent()
	if err != nil {
		return nil, err
	}
	flareSolverr.cycletlsC = NewCycletlsClient(ja3, ua)
	instances[URL] = flareSolverr
	return flareSolverr, nil
}

func (f *Flaresolverr) requestV1(req *V1RequestBase) (*V1ResponseBase, error) {
	resp, err := request().SetBody(req).Post(f.v1Url)
	if err != nil {
		return nil, err
	}
	var res V1ResponseBase
	err = json.Unmarshal(resp.Body(), &res)
	if err != nil {
		return nil, err
	}
	if res.Solution != nil && res.Solution.RawCookies != nil {
		for _, cookie := range res.Solution.RawCookies {
			sec := int64(cookie.Expires)
			nsec := int64((cookie.Expires - float64(sec)) * 1e9)
			res.Solution.Cookies = append(res.Solution.Cookies, &http.Cookie{
				Name:     cookie.Name,
				Value:    cookie.Value,
				Expires:  time.Unix(sec, nsec),
				Domain:   cookie.Domain,
				Path:     cookie.Path,
				Secure:   cookie.Secure,
				HttpOnly: cookie.HttpOnly,
			})
		}
	}
	switch v := res.Solution.RawResponse.(type) {
	case string:
		res.Solution.Response = res.Solution.RawResponse.(string)
	case map[string]any:
		jsonBytes, err := json.Marshal(v)
		if err != nil {
			return nil, err
		}
		res.Solution.Response = string(jsonBytes)
	}
	return &res, nil
}

func (f *Flaresolverr) GetV1(URL string, req *V1RequestBase) (*V1ResponseBase, error) {
	if req == nil {
		req = &V1RequestBase{
			MaxTimeout: 60000, // 60 秒
		}
	}
	req.Cmd = "request.get"
	req.URL = URL
	req.Session = f.sessionID

	return f.requestV1(req)
}

func (f *Flaresolverr) PostV1(URL string, req *V1RequestBase) (*V1ResponseBase, error) {
	if req == nil {
		req = &V1RequestBase{
			MaxTimeout: 60000, // 60 秒
		}
	}
	req.Cmd = "request.post"
	req.URL = URL
	req.Session = f.sessionID

	return f.requestV1(req)
}

func (f *Flaresolverr) CreateSessionV1(sessionID string) (*V1ResponseBase, error) {
	req := &V1RequestBase{
		Session: sessionID,
		Cmd:     "sessions.create",
	}
	return f.requestV1(req)
}

func (f *Flaresolverr) DestroySessionV1(sessionID string) (*V1ResponseBase, error) {
	req := &V1RequestBase{
		Session: sessionID,
		Cmd:     "sessions.destroy",
	}
	return f.requestV1(req)
}

func (f *Flaresolverr) ListSessionsV1(sessionID string) (*V1ResponseBase, error) {
	req := &V1RequestBase{
		Session: sessionID,
		Cmd:     "sessions.list",
	}
	return f.requestV1(req)
}

func (f *Flaresolverr) GetJa3AndUserAgent() (ua string, ja3 string, err error) {
	resp, err := f.GetV1("https://tls.peet.ws/api/tls", nil)
	if err != nil {
		return "", "", err
	}
	return resp.Solution.UserAgent, resp.Solution.RawResponse.(map[string]any)["tls"].(map[string]any)["ja3"].(string), nil
}

func (f *Flaresolverr) SimulateGet(URL string, opts *SimulateOptions) (cycletls.Response, error) {
	opts.Cookies = ConvertToCycletlsCookies(opts.HttpCookies)
	if f.proxy != "" {
		opts.Proxy = f.proxy
	}
	return f.cycletlsC.Get(URL, &opts.Options)
}

func (f *Flaresolverr) SimulatePost(URL string, opts *SimulateOptions) (cycletls.Response, error) {
	opts.Cookies = ConvertToCycletlsCookies(opts.HttpCookies)
	if f.proxy != "" {
		opts.Proxy = f.proxy
	}
	return f.cycletlsC.Post(URL, &opts.Options)
}

type SimulateOptions struct {
	cycletls.Options
	HttpCookies []*http.Cookie
}

func ConvertToCycletlsCookies(cookies []*http.Cookie) []cycletls.Cookie {
	var convertedCookies []cycletls.Cookie
	for _, cookie := range cookies {
		convertedCookies = append(convertedCookies, cycletls.Cookie{
			Name:     cookie.Name,
			Value:    cookie.Value,
			Path:     cookie.Path,
			Domain:   cookie.Domain,
			Expires:  cookie.Expires,
			MaxAge:   cookie.MaxAge,
			Secure:   cookie.Secure,
			HTTPOnly: cookie.HttpOnly,
			SameSite: cookie.SameSite,
		})
	}
	return convertedCookies
}

func ConvertToCookies(cookies []cycletls.Cookie) []*http.Cookie {
	var convertedCookies []*http.Cookie
	for _, cookie := range cookies {
		convertedCookies = append(convertedCookies, &http.Cookie{
			Name:     cookie.Name,
			Value:    cookie.Value,
			Path:     cookie.Path,
			Domain:   cookie.Domain,
			Expires:  cookie.Expires,
			MaxAge:   cookie.MaxAge,
			Secure:   cookie.Secure,
			HttpOnly: cookie.HTTPOnly,
			SameSite: cookie.SameSite,
		})
	}
	return convertedCookies
}