package grpcserver

import (
	"context"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/mrhid6/vantage/server/internal/checker"
	"github.com/mrhid6/vantage/server/internal/grpc/pb"
	"github.com/mrhid6/vantage/server/internal/models"
	"github.com/mrhid6/vantage/server/internal/services"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/encoding"
	"google.golang.org/grpc/keepalive"
	"google.golang.org/grpc/status"
)

func init() {
	encoding.RegisterCodec(JSONCodec{})
}

type vantageServer struct {
	pb.UnimplementedVantageServer
}

// Register carries no licence check, deliberately.
//
// A server row only ever comes from CreateServer, which checks the cap before
// issuing a pre-registration token. By the time an agent calls Register its row
// already exists, so counting here would count the caller itself: an instance
// sitting exactly at its cap would reject the very agent it just authorised, and
// every re-registration after a reinstall would fail too.
//
// The cap is enforced where rows are created, which is the only place it can be
// enforced correctly.
func (s *vantageServer) Register(ctx context.Context, req *pb.RegisterRequest) (*pb.RegisterResponse, error) {
	agentToken, err := services.RegisterServer(req.ServerId, req.PreRegToken, req.Hostname, req.IpAddress, req.OsInfo)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "registration failed: %v", err)
	}
	return &pb.RegisterResponse{AgentToken: agentToken}, nil
}

func (s *vantageServer) SyncKeys(ctx context.Context, req *pb.SyncRequest) (*pb.SyncResponse, error) {
	srv, err := services.ValidateAgentToken(req.ServerId, req.AgentToken)
	if err != nil {
		return nil, status.Errorf(codes.Unauthenticated, "invalid agent token")
	}

	if err := services.UpdateServerLastSeen(srv.ServerID, req.AgentVersion); err != nil {
		log.Printf("failed to update last seen for %s: %v", srv.ServerID, err)
	}

	if err := services.BackfillConsoleConfig(srv); err != nil {
		log.Printf("failed to backfill console config for %s: %v", srv.ServerID, err)
	}

	keys, err := services.BuildAuthorizedKeys(req.ServerId)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to build authorized keys: %v", err)
	}

	return &pb.SyncResponse{PublicKeys: keys}, nil
}

func (s *vantageServer) UploadGeneratedKey(ctx context.Context, req *pb.UploadKeyRequest) (*pb.UploadKeyResponse, error) {
	srv, err := services.ValidateAgentToken(req.ServerId, req.AgentToken)
	if err != nil {
		return nil, status.Errorf(codes.Unauthenticated, "invalid agent token")
	}

	key, err := services.CreateKey(srv.InstanceID, req.Label, req.PublicKey, "generated", srv.ServerID, req.PrivateKey, "")
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to store key: %v", err)
	}

	if _, err := services.AssignKey(srv.InstanceID, key.KeyID, srv.ServerID); err != nil {
		log.Printf("failed to auto-assign generated key: %v", err)
	}

	return &pb.UploadKeyResponse{KeyId: key.KeyID}, nil
}

func (s *vantageServer) ReportUpdates(ctx context.Context, req *pb.ReportUpdatesRequest) (*pb.ReportUpdatesResponse, error) {
	srv, err := services.ValidateAgentToken(req.ServerId, req.AgentToken)
	if err != nil {
		return nil, status.Errorf(codes.Unauthenticated, "invalid agent token")
	}

	pkgs := make([]models.PackageUpdate, len(req.Updates))
	for i, u := range req.Updates {
		pkgs[i] = models.PackageUpdate{
			Name:           u.Name,
			CurrentVersion: u.CurrentVersion,
			NewVersion:     u.NewVersion,
		}
	}
	if err := services.StoreAvailableUpdates(srv.ServerID, pkgs); err != nil {
		log.Printf("failed to store updates for %s: %v", srv.ServerID, err)
	}
	return &pb.ReportUpdatesResponse{}, nil
}

func (s *vantageServer) ReportInventory(ctx context.Context, req *pb.InventoryReport) (*pb.InventoryReportResponse, error) {
	srv, err := services.ValidateAgentToken(req.ServerId, req.AgentToken)
	if err != nil {
		return nil, status.Errorf(codes.Unauthenticated, "invalid agent token")
	}
	if err := services.StoreInventory(srv.ServerID, req); err != nil {
		log.Printf("store inventory for %s: %v", srv.ServerID, err)
	}
	return &pb.InventoryReportResponse{}, nil
}

func (s *vantageServer) SyncMonitors(ctx context.Context, req *pb.SyncMonitorsRequest) (*pb.SyncMonitorsResponse, error) {
	srv, err := services.ValidateAgentToken(req.ServerId, req.AgentToken)
	if err != nil {
		return nil, status.Errorf(codes.Unauthenticated, "invalid agent token")
	}
	monitors, err := services.ListMonitorsForRunner(srv.InstanceID, srv.ServerID)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "list monitors")
	}
	specs := make([]pb.MonitorSpec, 0, len(monitors))
	for _, m := range monitors {
		specs = append(specs, pb.MonitorSpec{
			MonitorId:      m.MonitorID,
			Type:           m.Type,
			URL:            m.Target.URL,
			Host:           m.Target.Host,
			Port:           m.Target.Port,
			Method:         m.Target.Method,
			ExpectedStatus: m.Target.ExpectedStatus,
			Keyword:        m.Target.Keyword,
			TLSWarnDays:    m.Target.TLSWarnDays,
			Insecure:       m.Target.Insecure,
			IntervalSec:    m.IntervalSec,
			Retries:        m.Retries,
		})
	}
	return &pb.SyncMonitorsResponse{Monitors: specs}, nil
}

func (s *vantageServer) ReportChecks(ctx context.Context, req *pb.ReportChecksRequest) (*pb.ReportChecksResponse, error) {
	srv, err := services.ValidateAgentToken(req.ServerId, req.AgentToken)
	if err != nil {
		return nil, status.Errorf(codes.Unauthenticated, "invalid agent token")
	}
	for _, r := range req.Results {
		res := checker.Result{Up: r.Up, LatencyMs: r.LatencyMs, Message: r.Message}
		if r.CertExpiryUnix > 0 {
			t := time.Unix(r.CertExpiryUnix, 0)
			res.CertExpiry = &t
		}
		if err := services.IngestResult(srv.InstanceID, srv.ServerID, r.MonitorId, res); err != nil {
			log.Printf("ingest check %s: %v", r.MonitorId, err)
		}
	}
	return &pb.ReportChecksResponse{}, nil
}

func (s *vantageServer) CommandStream(stream pb.Vantage_CommandStreamServer) error {

	msg, err := stream.Recv()
	if err != nil {
		return status.Errorf(codes.InvalidArgument, "expected initial auth message: %v", err)
	}

	srv, err := services.ValidateAgentToken(msg.ServerId, msg.AgentToken)
	if err != nil {
		return status.Errorf(codes.Unauthenticated, "invalid agent token")
	}

	if err := services.UpdateServerLastSeen(srv.ServerID, ""); err != nil {
		log.Printf("update last seen %s: %v", srv.ServerID, err)
	}

	ch := services.Dispatcher.Connect(srv.ServerID)
	defer services.Dispatcher.Disconnect(srv.ServerID)

	log.Printf("agent %s connected command stream", srv.ServerID)
	defer log.Printf("agent %s disconnected command stream", srv.ServerID)

	go func() {
		for {
			m, err := stream.Recv()
			if err != nil {
				return
			}
			if m.Result != nil {
				r := m.Result
				log.Printf("agent %s cmd %s: success=%v %s", srv.ServerID, r.CommandId, r.Success, r.Message)
			}
			if m.StepResult != nil {
				services.StepResults.Deliver(m.StepResult)
			}
			if m.StepOutput != nil {
				if m.StepOutput.Eof {
					services.StepLogs.Close(m.StepOutput.CommandId)
				} else {
					services.StepLogs.Append(m.StepOutput.CommandId, m.StepOutput.Data)
				}
			}
		}
	}()

	ctx := stream.Context()
	for {
		select {
		case <-ctx.Done():
			return nil
		case cmd, ok := <-ch:
			if !ok {
				return nil
			}
			if err := stream.Send(cmd); err != nil {
				return err
			}
		}
	}
}

func StartGRPC(port int) error {
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		return fmt.Errorf("failed to listen: %w", err)
	}

	s := grpc.NewServer(

		grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
			MinTime:             20 * time.Second,
			PermitWithoutStream: false,
		}),
		grpc.KeepaliveParams(keepalive.ServerParameters{

			Time:    45 * time.Second,
			Timeout: 10 * time.Second,
		}),
	)
	pb.RegisterVantageServer(s, &vantageServer{})

	log.Printf("gRPC server listening on :%d", port)
	return s.Serve(lis)
}
