package rewriter

import (
	"net/http"

	"forge.cadoles.com/cadoles/bouncer/internal/rule"
	ruleHTTP "forge.cadoles.com/cadoles/bouncer/internal/rule/http"
	"github.com/pkg/errors"
)

type RequestEnv struct {
	Request RequestInfo `expr:"request"`
}

type URLEnv struct {
	Scheme      string      `expr:"scheme"`
	Opaque      string      `expr:"opaque"`
	User        UserInfoEnv `expr:"user"`
	Host        string      `expr:"host"`
	Path        string      `expr:"path"`
	RawPath     string      `expr:"rawPath"`
	RawQuery    string      `expr:"rawQuery"`
	Fragment    string      `expr:"fragment"`
	RawFragment string      `expr:"rawFragment"`
}

type UserInfoEnv struct {
	Username string `expr:"username"`
	Password string `expr:"password"`
}

type RequestInfo struct {
	Method           string              `expr:"method"`
	URL              URLEnv              `expr:"url"`
	RawURL           string              `expr:"rawUrl"`
	Proto            string              `expr:"proto"`
	ProtoMajor       int                 `expr:"protoMajor"`
	ProtoMinor       int                 `expr:"protoMinor"`
	Header           map[string][]string `expr:"header"`
	ContentLength    int64               `expr:"contentLength"`
	TransferEncoding []string            `expr:"transferEncoding"`
	Host             string              `expr:"host"`
	Trailer          map[string][]string `expr:"trailer"`
	RemoteAddr       string              `expr:"remoteAddr"`
	RequestURI       string              `expr:"requestUri"`
}

func (l *Layer) applyRequestRules(r *http.Request, options *LayerOptions) error {
	rules := options.Rules.Request
	if len(rules) == 0 {
		return nil
	}

	engine, err := l.getRequestRuleEngine(r, options)
	if err != nil {
		return errors.WithStack(err)
	}

	env := &RequestEnv{
		Request: RequestInfo{
			Method: r.Method,
			URL: URLEnv{
				Scheme: r.URL.Scheme,
				Opaque: r.URL.Opaque,
				User: UserInfoEnv{
					Username: r.URL.User.Username(),
					Password: func() string {
						passwd, _ := r.URL.User.Password()
						return passwd
					}(),
				},
				Host:        r.URL.Host,
				Path:        r.URL.Path,
				RawPath:     r.URL.RawPath,
				RawQuery:    r.URL.RawQuery,
				Fragment:    r.URL.Fragment,
				RawFragment: r.URL.RawFragment,
			},
			RawURL:           r.URL.String(),
			Proto:            r.Proto,
			ProtoMajor:       r.ProtoMajor,
			ProtoMinor:       r.ProtoMinor,
			Header:           r.Header,
			ContentLength:    r.ContentLength,
			TransferEncoding: r.TransferEncoding,
			Host:             r.Host,
			Trailer:          r.Trailer,
			RemoteAddr:       r.RemoteAddr,
			RequestURI:       r.RequestURI,
		},
	}

	if _, err := engine.Apply(env); err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func (l *Layer) getRequestRuleEngine(r *http.Request, options *LayerOptions) (*rule.Engine[*RequestEnv], error) {
	engine, err := rule.NewEngine[*RequestEnv](
		rule.WithRules(options.Rules.Request...),
		ruleHTTP.WithRequestFuncs(r),
	)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return engine, nil
}

type ResponseEnv struct {
	Request  RequestInfo  `expr:"request"`
	Response ResponseInfo `expr:"response"`
}

type ResponseInfo struct {
	Status           string              `expr:"status"`
	StatusCode       int                 `expr:"statusCode"`
	Proto            string              `expr:"proto"`
	ProtoMajor       int                 `expr:"protoMajor"`
	ProtoMinor       int                 `expr:"protoMinor"`
	Header           map[string][]string `expr:"header"`
	ContentLength    int64               `expr:"contentLength"`
	TransferEncoding []string            `expr:"transferEncoding"`
	Uncompressed     bool                `expr:"uncompressed"`
	Trailer          map[string][]string `expr:"trailer"`
}

func (l *Layer) applyResponseRules(r *http.Response, options *LayerOptions) error {
	rules := options.Rules.Response
	if len(rules) == 0 {
		return nil
	}

	engine, err := l.getResponseRuleEngine(r, options)
	if err != nil {
		return errors.WithStack(err)
	}

	env := &ResponseEnv{
		Request: RequestInfo{
			Method: r.Request.Method,
			URL: URLEnv{
				Scheme: r.Request.URL.Scheme,
				Opaque: r.Request.URL.Opaque,
				User: UserInfoEnv{
					Username: r.Request.URL.User.Username(),
					Password: func() string {
						passwd, _ := r.Request.URL.User.Password()
						return passwd
					}(),
				},
				Host:        r.Request.URL.Host,
				Path:        r.Request.URL.Path,
				RawPath:     r.Request.URL.RawPath,
				RawQuery:    r.Request.URL.RawQuery,
				Fragment:    r.Request.URL.Fragment,
				RawFragment: r.Request.URL.RawFragment,
			},
			RawURL:           r.Request.URL.String(),
			Proto:            r.Request.Proto,
			ProtoMajor:       r.Request.ProtoMajor,
			ProtoMinor:       r.Request.ProtoMinor,
			Header:           r.Request.Header,
			ContentLength:    r.Request.ContentLength,
			TransferEncoding: r.Request.TransferEncoding,
			Host:             r.Request.Host,
			Trailer:          r.Request.Trailer,
			RemoteAddr:       r.Request.RemoteAddr,
			RequestURI:       r.Request.RequestURI,
		},
		Response: ResponseInfo{
			Proto:            r.Proto,
			ProtoMajor:       r.ProtoMajor,
			ProtoMinor:       r.ProtoMinor,
			Header:           r.Header,
			ContentLength:    r.ContentLength,
			TransferEncoding: r.TransferEncoding,
			Trailer:          r.Trailer,
			Status:           r.Status,
			StatusCode:       r.StatusCode,
		},
	}

	if _, err := engine.Apply(env); err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func (l *Layer) getResponseRuleEngine(r *http.Response, options *LayerOptions) (*rule.Engine[*ResponseEnv], error) {
	engine, err := rule.NewEngine[*ResponseEnv](
		rule.WithRules(options.Rules.Response...),
		ruleHTTP.WithResponseFuncs(r),
	)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return engine, nil
}