// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"errors"
	"fmt"
	"net"
	"os"
	"path/filepath"

	"github.com/harness/gitness/gitrpc/internal/middleware"
	"github.com/harness/gitness/gitrpc/internal/service"
	"github.com/harness/gitness/gitrpc/internal/storage"
	"github.com/harness/gitness/gitrpc/rpc"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
	"google.golang.org/grpc"
	"google.golang.org/grpc/keepalive"
)

const (
	repoSubdirName           = "repos"
	ReposGraveyardSubdirName = "cleanup"
)

type GRPCServer struct {
	*grpc.Server
	Port int
}

func NewServer(config Config, adapter service.GitAdapter) (*GRPCServer, error) {
	if err := config.Validate(); err != nil {
		return nil, fmt.Errorf("configuration is invalid: %w", err)
	}
	// Create repos folder
	reposRoot := filepath.Join(config.GitRoot, repoSubdirName)
	if _, err := os.Stat(reposRoot); errors.Is(err, os.ErrNotExist) {
		if err = os.MkdirAll(reposRoot, 0o700); err != nil {
			return nil, err
		}
	}

	// interceptors
	errIntc := middleware.NewErrInterceptor()
	logIntc := middleware.NewLogInterceptor()

	s := grpc.NewServer(
		grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
			grpc_recovery.UnaryServerInterceptor(),
			logIntc.UnaryInterceptor(),
			errIntc.UnaryInterceptor(),
		)),
		grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
			grpc_recovery.StreamServerInterceptor(),
			logIntc.StreamInterceptor(),
			errIntc.StreamInterceptor(),
		)),
		grpc.KeepaliveParams(keepalive.ServerParameters{
			MaxConnectionAge:      config.MaxConnAge,
			MaxConnectionAgeGrace: config.MaxConnAgeGrace,
		}),
	)
	store := storage.NewLocalStore()
	// create a temp dir for deleted repositories
	// this dir should get cleaned up peridocally if it's not empty
	reposGraveyard := filepath.Join(config.GitRoot, ReposGraveyardSubdirName)
	if _, errdir := os.Stat(reposGraveyard); os.IsNotExist(errdir) {
		if errdir = os.MkdirAll(reposGraveyard, 0o700); errdir != nil {
			return nil, errdir
		}
	}
	// initialize services
	repoService, err := service.NewRepositoryService(adapter, store, reposRoot, config.TmpDir,
		config.GitHookPath, reposGraveyard)
	if err != nil {
		return nil, err
	}
	refService, err := service.NewReferenceService(adapter, reposRoot, config.TmpDir)
	if err != nil {
		return nil, err
	}
	httpService, err := service.NewHTTPService(adapter, reposRoot)
	if err != nil {
		return nil, err
	}
	commitFilesService, err := service.NewCommitFilesService(adapter, reposRoot, config.TmpDir)
	if err != nil {
		return nil, err
	}
	diffService, err := service.NewDiffService(adapter, reposRoot, config.TmpDir)
	if err != nil {
		return nil, err
	}
	mergeService, err := service.NewMergeService(adapter, reposRoot, config.TmpDir)
	if err != nil {
		return nil, err
	}
	blameService := service.NewBlameService(adapter, reposRoot)
	pushService := service.NewPushService(adapter, reposRoot)

	// register services
	rpc.RegisterRepositoryServiceServer(s, repoService)
	rpc.RegisterReferenceServiceServer(s, refService)
	rpc.RegisterSmartHTTPServiceServer(s, httpService)
	rpc.RegisterCommitFilesServiceServer(s, commitFilesService)
	rpc.RegisterDiffServiceServer(s, diffService)
	rpc.RegisterMergeServiceServer(s, mergeService)
	rpc.RegisterBlameServiceServer(s, blameService)
	rpc.RegisterPushServiceServer(s, pushService)

	return &GRPCServer{
		Server: s,
		Port:   config.Port,
	}, nil
}

func (s *GRPCServer) Start() error {
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Port))
	if err != nil {
		return err
	}
	return s.Server.Serve(lis)
}

func (s *GRPCServer) Stop() error {
	s.Server.GracefulStop()
	return nil
}