package project

import (
	"encoding/json"
	"fmt"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"

	"github.com/pingcap-inc/security/baseline/api/v1/controller"
	"github.com/pingcap-inc/security/baseline/api/v1/controller/errors"
	"github.com/pingcap-inc/security/baseline/api/v1/model"
)

type createProjectReq struct {
	Name string `json:"name" validate:"required,min=1,max=64"`
}

type createProjectRsp struct {
	Project *model.ProjectModel `json:"project"`
}

func createNewProject(tx *gorm.DB, req *createProjectReq) (*createProjectRsp, error) {
	project := model.ProjectModel{
		Name: req.Name,
	}

	err := tx.Create(&project).Error

	return &createProjectRsp{
		Project: &project,
	}, err
}

func createProjectHandler(ctx *gin.Context, rpcReq *controller.RpcRequest) (*controller.RpcResponse, error) {
	req := rpcReq.Request.(*createProjectReq)
	rsp, err := createNewProject(ctx.MustGet("db").(*gorm.DB), req)
	if err != nil {
		return nil, err
	} else {
		if response, err := json.Marshal(rsp); err != nil {
			return nil, err
		} else {
			return &controller.RpcResponse{
				Code:     errors.BL_SUCCESS,
				Response: string(response),
			}, err
		}
	}
}

// Profile Project profile
// @Summary Create Project
// @Description Create a new project
// @Tags Project
// @Accept application/json
// @Produce application/json
// @Param createProjectReq body createProjectReq true "create project request"
// @Success 200 {object} controller.CommonResult{data=createProjectRsp}
// @Failure 500 {object} controller.CommonResult
// @Router /project [post]
func CreateProjectHandler(ctx *gin.Context) {
	if requestBody, ok := controller.HandleJsonRequestFromBody(ctx, &createProjectReq{}); ok {
		controller.InvokeHandler(ctx, createProjectHandler, requestBody, &createProjectReq{}, &createProjectRsp{})
	}
}

type getProjectInfoReq struct{}

type projectInfo struct {
	ID         uint   `json:"id"`
	Name       string `json:"name"`
	Image      int    `json:"image"`
	Repository int    `json:"repository"`
	Total      int    `json:"total"`
}

type getProjectInfoRsp struct {
	Project *projectInfo `json:"project"`
}

func findProjectInfoById(tx *gorm.DB, project_id string) (*getProjectInfoRsp, error) {
	project := model.ProjectModel{}
	tasks := []model.TaskModel{}

	err := tx.Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("id == ?", project_id).First(&project).Error; err != nil {
			return err
		} else {
			return tx.Where("project_id == ?", project_id).Find(&tasks).Error
		}
	})

	if err != nil {
		return nil, err
	} else {
		info := projectInfo{
			ID:   project.ID,
			Name: project.Name,
		}

		for _, task := range tasks {
			switch task.Type {
			case "image":
				info.Image++
			case "repository":
				info.Repository++
			}
			info.Total++
		}

		return &getProjectInfoRsp{
			Project: &info,
		}, err
	}
}

func getProjectInfoHandler(ctx *gin.Context, rpcReq *controller.RpcRequest) (*controller.RpcResponse, error) {
	rsp, err := findProjectInfoById(ctx.MustGet("db").(*gorm.DB), ctx.Param("project_id"))
	if err != nil {
		return nil, err
	} else {
		if response, err := json.Marshal(rsp); err != nil {
			return nil, err
		} else {
			return &controller.RpcResponse{
				Code:     errors.BL_SUCCESS,
				Response: string(response),
			}, err
		}
	}
}

// Profile Project profile
// @Summary Get Project Info
// @Description Get project info in pages
// @Tags Project
// @Produce application/json
// @Param project_id path int true "project id"
// @Param getProjectInfoReq query getProjectInfoReq true "page number of project info"
// @Success 200 {object} controller.CommonResult{data=getProjectInfoRsp}
// @Failure 500 {object} controller.CommonResult
// @Router /project/{project_id} [get]
func GetProjectInfoHandler(ctx *gin.Context) {
	if requestBody, ok := controller.HandleJsonRequestFromQuery(ctx, &getProjectInfoReq{}); ok {
		controller.InvokeHandler(ctx, getProjectInfoHandler, requestBody, &getProjectInfoReq{}, &getProjectInfoRsp{})
	}
}

type getProjectListReq struct {
	controller.PageRequest
}

type getProjectListRsp struct {
	Projects []projectInfo `json:"projects"`
}

func findAllProjectsInPage(tx *gorm.DB, req *getProjectListReq) (*getProjectListRsp, *controller.Page, error) {
	projects := []model.ProjectModel{}
	projectsInfo := []projectInfo{}
	page := controller.Page{
		PageRequest: req.PageRequest,
	}

	err := tx.Transaction(func(tx *gorm.DB) error {
		allProjects := tx.Find(&[]model.ProjectModel{})
		if allProjects.Error != nil {
			return allProjects.Error
		} else {
			allProjects.Count(&page.Total)
			return allProjects.Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Order("updated_at desc").Find(&projects).Error
		}
	})

	if err != nil {
		return nil, &page, err
	} else {
		for _, project := range projects {
			rsp, err := findProjectInfoById(tx, fmt.Sprintf("%d", project.ID))
			if err != nil {
				return nil, &page, err
			} else {
				projectsInfo = append(projectsInfo, *rsp.Project)
			}
		}

		return &getProjectListRsp{
			Projects: projectsInfo,
		}, &page, nil
	}
}

func getProjectListHandler(ctx *gin.Context, rpcReq *controller.RpcRequest) (*controller.RpcResponse, error) {
	req := rpcReq.Request.(*getProjectListReq)
	rsp, page, err := findAllProjectsInPage(ctx.MustGet("db").(*gorm.DB), req)
	if err != nil {
		return nil, err
	} else {
		if response, err := json.Marshal(rsp); err != nil {
			return nil, err
		} else {
			return &controller.RpcResponse{
				Code:     errors.BL_SUCCESS,
				Response: string(response),
				Page:     page,
			}, err
		}
	}
}

// Profile Project profile
// @Summary Get Project List
// @Description Get project list in pages
// @Tags Project
// @Produce application/json
// @Param getProjectListReq query getProjectListReq true "page number of project list"
// @Success 200 {object} controller.CommonResult{data=getProjectListRsp}
// @Failure 500 {object} controller.CommonResult
// @Router /project [get]
func GetProjectListHandler(ctx *gin.Context) {
	if requestBody, ok := controller.HandleJsonRequestFromQuery(ctx, &getProjectListReq{
		PageRequest: controller.PageRequest{
			PageSize: 20,
		},
	}); ok {
		controller.InvokeHandler(ctx, getProjectListHandler, requestBody, &getProjectListReq{}, &getProjectListRsp{})
	}
}

type getProjectReportReq struct{}

type getProjectReportRsp struct {
	Project *projectInfo       `json:"project"`
	Tasks   []getTaskReportRsp `json:"tasks"`
}

func findProjectReportById(tx *gorm.DB, project_id string) (*getProjectReportRsp, error) {
	project := model.ProjectModel{}
	tasks := []model.TaskModel{}

	err := tx.Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("id == ?", project_id).First(&project).Error; err != nil {
			return err
		} else {
			return tx.Where("project_id == ?", project_id).Find(&tasks).Error
		}
	})

	if err != nil {
		return nil, err
	} else {
		if info, err := findProjectInfoById(tx, project_id); err != nil {
			return nil, err
		} else {
			tasksReport := []getTaskReportRsp{}

			for _, task := range tasks {
				rsp, err := findTaskReportById(tx, project_id, fmt.Sprintf("%d", task.ID))
				if err != nil {
					return nil, err
				} else {
					tasksReport = append(tasksReport, *rsp)
				}
			}

			return &getProjectReportRsp{
				Project: info.Project,
				Tasks:   tasksReport,
			}, err
		}
	}
}

func getProjectReportHandler(ctx *gin.Context, rpcReq *controller.RpcRequest) (*controller.RpcResponse, error) {
	rsp, err := findProjectReportById(ctx.MustGet("db").(*gorm.DB), ctx.Param("project_id"))
	if err != nil {
		return nil, err
	} else {
		if response, err := json.Marshal(rsp); err != nil {
			return nil, err
		} else {
			return &controller.RpcResponse{
				Code:     errors.BL_SUCCESS,
				Response: string(response),
			}, err
		}
	}
}

// Profile Project profile
// @Summary Get Project Report
// @Description Get specific project report
// @Tags Project
// @Accept application/json
// @Produce application/json
// @Param project_id path int true "project id"
// @Param getProjectReportReq query getProjectReportReq true "get project report request"
// @Success 200 {object} controller.CommonResult{data=getProjectReportRsp}
// @Failure 500 {object} controller.CommonResult
// @Router /project/{project_id}/report [get]
func GetProjectReportHandler(ctx *gin.Context) {
	if requestBody, ok := controller.HandleJsonRequestFromQuery(ctx, &getProjectReportReq{}); ok {
		controller.InvokeHandler(ctx, getProjectReportHandler, requestBody, &getProjectReportReq{}, &getProjectReportRsp{})
	}
}

type deleteProjectReq struct{}
type deleteProjectRsp struct{}

func deleteProjectById(tx *gorm.DB, project_id string) (*deleteProjectRsp, error) {
	err := tx.Transaction(func(tx *gorm.DB) error {
		project := model.ProjectModel{}
		if err := tx.Where("id == ?", project_id).First(&project).Error; err != nil {
			return err
		} else {
			tasks := []model.TaskModel{}
			if err := tx.Where("project_id == ?", project_id).Find(&tasks).Error; err != nil {
				return err
			} else {
				for _, task := range tasks {
					_, err := deleteTaskById(tx, project_id, fmt.Sprintf("%d", task.ID))
					if err != nil {
						return err
					}
				}
			}

			return tx.Delete(&project).Error
		}
	})

	return &deleteProjectRsp{}, err
}

func deleteProjectHandler(ctx *gin.Context, rpcReq *controller.RpcRequest) (*controller.RpcResponse, error) {
	rsp, err := deleteProjectById(ctx.MustGet("db").(*gorm.DB), ctx.Param("project_id"))
	if err != nil {
		return nil, err
	} else {
		if response, err := json.Marshal(rsp); err != nil {
			return nil, err
		} else {
			return &controller.RpcResponse{
				Code:     errors.BL_SUCCESS,
				Response: string(response),
			}, err
		}
	}
}

// Profile Project profile
// @Summary Delete Project
// @Description Delete project
// @Tags Project
// @Produce application/json
// @Param project_id path int true "project id"
// @Success 200 {object} controller.CommonResult{data=deleteProjectRsp}
// @Failure 500 {object} controller.CommonResult
// @Router /project/{project_id} [delete]
func DeleteProjectHandler(ctx *gin.Context) {
	if requestBody, ok := controller.HandleJsonRequestFromQuery(ctx, &deleteProjectReq{}); ok {
		controller.InvokeHandler(ctx, deleteProjectHandler, requestBody, &deleteProjectReq{}, &deleteProjectRsp{})
	}
}
