package portforwarding

import (
	"context"
	"time"

	"connectrpc.com/connect"
	"git.kocoder.xyz/kocoder/portforwarder/api/types/v1alpha1"
	csv1 "git.kocoder.xyz/kocoder/portforwarder/clientset/v1alpha1"
	portforwardingv1 "git.kocoder.xyz/kocoder/portforwarder/gen/portforwarding"
	"git.kocoder.xyz/kocoder/portforwarder/gen/portforwarding/portforwardingv1connect"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type PortforwardingService struct {
	ir         csv1.IngressrouteInterface
	ns         string
	svc        string
	basedomain string
	tlssecret  string
}

func NewPortforwardingService(ingressroutehandler csv1.IngressrouteInterface, namespace string, servicename string, basedomain string, secretname string) portforwardingv1connect.PortForwardingServiceHandler {
	return &PortforwardingService{ir: ingressroutehandler, ns: namespace, svc: servicename, basedomain: basedomain, tlssecret: secretname}
}

// CreatePortforwarding implements portforwardingv1connect.PortForwardingServiceHandler.
func (p *PortforwardingService) CreatePortforwarding(ctx context.Context, req *portforwardingv1.CreatePortforwardingRequest) (*portforwardingv1.CreatePortforwardingResponse, error) {
	ir := &v1alpha1.IngressRoute{
		ObjectMeta: v1.ObjectMeta{
			Name:   req.Alias,
			Labels: map[string]string{"alias": req.Alias, "mangedBy": "heek-portforwarder"},
		},
		Spec: v1alpha1.IngressRouteSpec{
			EntryPoints: []string{"websecure", "web"},
			Routes: []v1alpha1.Route{
				{
					Kind:  "Rule",
					Match: "Host(`" + req.Alias + p.basedomain + "`)",
					Services: []v1alpha1.Service{
						{
							Kind:               "Service",
							Name:               p.svc,
							Namespace:          p.ns,
							PassHostHeader:     true,
							Port:               int(req.TargetPort),
							ResponseForwarding: v1alpha1.ResponseForwarding{FlushInterval: "10ms"},
							Scheme:             "http",
							Sticky:             v1alpha1.Sticky{Cookie: v1alpha1.Cookie{HttpOnly: false, Name: "", Secure: false}},
							Strategy:           "wrr",
							Weight:             10,
						},
					},
					Middlewares:   []v1alpha1.Middleware{},
					Observability: v1alpha1.Observability{AccessLogs: false, Metrics: false, Tracing: false},
					Priority:      10,
				},
			},
		},
	}

	downstreamContext, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*2))

	ir, err := p.ir.Create(downstreamContext, ir)

	cancel()

	if err != nil {
		return nil, connect.NewError(connect.CodeInternal, err)
	}

	return &portforwardingv1.CreatePortforwardingResponse{
		Success: true,
		Pf: &portforwardingv1.Portforwarding{
			Alias:      ir.Name,
			TargetPort: int32(ir.Spec.Routes[0].Services[0].Port),
		},
	}, nil
}

// DeletePortforwarding implements portforwardingv1connect.PortForwardingServiceHandler.
func (p *PortforwardingService) DeletePortforwarding(ctx context.Context, req *portforwardingv1.DeletePortforwardingRequest) (*portforwardingv1.DeletePortforwardingResponse, error) {
	downstreamContext, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*2))

	opts := *v1.NewDeleteOptions(1)
	err := p.ir.Delete(downstreamContext, req.Alias, opts)

	cancel()

	if err != nil {
		return nil, connect.NewError(connect.CodeInternal, err)
	}

	return &portforwardingv1.DeletePortforwardingResponse{
		Success: true,
	}, nil
}

// ListPortforwardings implements portforwardingv1connect.PortForwardingServiceHandler.
func (p *PortforwardingService) ListPortforwardings(ctx context.Context, req *portforwardingv1.ListPortforwardingsRequest) (*portforwardingv1.ListPortforwardingsResponse, error) {
	downstreamContext, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*2))

	opts := v1.ListOptions{}
	res, err := p.ir.List(downstreamContext, opts)

	cancel()

	if err != nil {
		return nil, connect.NewError(connect.CodeInternal, err)
	}

	items := res.Items
	pf := make([]*portforwardingv1.Portforwarding, len(items))

	for i, v := range items {
		pf[i] = &portforwardingv1.Portforwarding{
			Alias:      v.Name,
			TargetPort: int32(v.Spec.Routes[0].Services[0].Port),
		}
	}

	return &portforwardingv1.ListPortforwardingsResponse{
		Pf: pf,
	}, nil
}
