package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
	"github.com/mattermost/focalboard/server/model"

	mm_model "github.com/mattermost/mattermost/server/public/model"
	"github.com/mattermost/mattermost/server/public/shared/mlog"
)

const (
	complianceDefaultPage    = "0"
	complianceDefaultPerPage = "60"
)

func (a *API) registerComplianceRoutes(r *mux.Router) {
	// Compliance APIs
	r.HandleFunc("/admin/boards", a.sessionRequired(a.handleGetBoardsForCompliance)).Methods("GET")
	r.HandleFunc("/admin/boards_history", a.sessionRequired(a.handleGetBoardsComplianceHistory)).Methods("GET")
	r.HandleFunc("/admin/blocks_history", a.sessionRequired(a.handleGetBlocksComplianceHistory)).Methods("GET")
}

func (a *API) handleGetBoardsForCompliance(w http.ResponseWriter, r *http.Request) {
	// swagger:operation GET /admin/boards getBoardsForCompliance
	//
	// Returns boards for a specific team, or all teams.
	//
	// Requires a license that includes Compliance feature. Caller must have `manage_system` permissions.
	//
	// ---
	// produces:
	// - application/json
	// parameters:
	// - name: team_id
	//   in: query
	//   description: Team ID. If empty then boards across all teams are included.
	//   required: false
	//   type: string
	// - name: page
	//   in: query
	//   description: The page to select (default=0)
	//   required: false
	//   type: integer
	// - name: per_page
	//   in: query
	//   description: Number of boards to return per page(default=60)
	//   required: false
	//   type: integer
	// security:
	// - BearerAuth: []
	// responses:
	//   '200':
	//     description: success
	//     schema:
	//       type: object
	//       items:
	//         "$ref": "#/definitions/BoardsComplianceResponse"
	//   default:
	//     description: internal error
	//     schema:
	//       "$ref": "#/definitions/ErrorResponse"

	query := r.URL.Query()
	teamID := query.Get("team_id")
	strPage := query.Get("page")
	strPerPage := query.Get("per_page")

	// check for permission `manage_system`
	userID := getUserID(r)
	if !a.permissions.HasPermissionTo(userID, mm_model.PermissionManageSystem) {
		a.errorResponse(w, r, model.NewErrUnauthorized("access denied Compliance Export getAllBoards"))
		return
	}

	// check for valid license feature: compliance
	license := a.app.GetLicense()
	if license == nil || !(*license.Features.Compliance) {
		a.errorResponse(w, r, model.NewErrNotImplemented("insufficient license Compliance Export getAllBoards"))
		return
	}

	// check for valid team if specified
	if teamID != "" {
		_, err := a.app.GetTeam(teamID)
		if err != nil {
			a.errorResponse(w, r, model.NewErrBadRequest("invalid team id: "+teamID))
			return
		}
	}

	if strPage == "" {
		strPage = complianceDefaultPage
	}
	if strPerPage == "" {
		strPerPage = complianceDefaultPerPage
	}
	page, err := strconv.Atoi(strPage)
	if err != nil {
		message := fmt.Sprintf("invalid `page` parameter: %s", err)
		a.errorResponse(w, r, model.NewErrBadRequest(message))
		return
	}
	perPage, err := strconv.Atoi(strPerPage)
	if err != nil {
		message := fmt.Sprintf("invalid `per_page` parameter: %s", err)
		a.errorResponse(w, r, model.NewErrBadRequest(message))
		return
	}

	opts := model.QueryBoardsForComplianceOptions{
		TeamID:  teamID,
		Page:    page,
		PerPage: perPage,
	}

	boards, more, err := a.app.GetBoardsForCompliance(opts)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	a.logger.Debug("GetBoardsForCompliance",
		mlog.String("teamID", teamID),
		mlog.Int("boardsCount", len(boards)),
		mlog.Bool("hasNext", more),
	)

	response := model.BoardsComplianceResponse{
		HasNext: more,
		Results: boards,
	}
	data, err := json.Marshal(response)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	jsonBytesResponse(w, http.StatusOK, data)
}

func (a *API) handleGetBoardsComplianceHistory(w http.ResponseWriter, r *http.Request) {
	// swagger:operation GET /admin/boards_history getBoardsComplianceHistory
	//
	// Returns boards histories for a specific team, or all teams.
	//
	// Requires a license that includes Compliance feature. Caller must have `manage_system` permissions.
	//
	// ---
	// produces:
	// - application/json
	// parameters:
	// - name: modified_since
	//   in: query
	//   description: Filters for boards modified since timestamp; Unix time in milliseconds
	//   required: true
	//   type: integer
	// - name: include_deleted
	//   in: query
	//   description: When true then deleted boards are included. Default=false
	//   required: false
	//   type: boolean
	// - name: team_id
	//   in: query
	//   description: Team ID. If empty then board histories across all teams are included
	//   required: false
	//   type: string
	// - name: page
	//   in: query
	//   description: The page to select (default=0)
	//   required: false
	//   type: integer
	// - name: per_page
	//   in: query
	//   description: Number of board histories to return per page (default=60)
	//   required: false
	//   type: integer
	// security:
	// - BearerAuth: []
	// responses:
	//   '200':
	//     description: success
	//     schema:
	//       type: object
	//       items:
	//         "$ref": "#/definitions/BoardsComplianceHistoryResponse"
	//   default:
	//     description: internal error
	//     schema:
	//       "$ref": "#/definitions/ErrorResponse"

	query := r.URL.Query()
	strModifiedSince := query.Get("modified_since") // required, everything else optional
	includeDeleted := query.Get("include_deleted") == "true"
	strPage := query.Get("page")
	strPerPage := query.Get("per_page")
	teamID := query.Get("team_id")

	if strModifiedSince == "" {
		a.errorResponse(w, r, model.NewErrBadRequest("`modified_since` parameter required"))
		return
	}

	// check for permission `manage_system`
	userID := getUserID(r)
	if !a.permissions.HasPermissionTo(userID, mm_model.PermissionManageSystem) {
		a.errorResponse(w, r, model.NewErrUnauthorized("access denied Compliance Export getBoardsHistory"))
		return
	}

	// check for valid license feature: compliance
	license := a.app.GetLicense()
	if license == nil || !(*license.Features.Compliance) {
		a.errorResponse(w, r, model.NewErrNotImplemented("insufficient license Compliance Export getBoardsHistory"))
		return
	}

	// check for valid team if specified
	if teamID != "" {
		_, err := a.app.GetTeam(teamID)
		if err != nil {
			a.errorResponse(w, r, model.NewErrBadRequest("invalid team id: "+teamID))
			return
		}
	}

	if strPage == "" {
		strPage = complianceDefaultPage
	}
	if strPerPage == "" {
		strPerPage = complianceDefaultPerPage
	}
	page, err := strconv.Atoi(strPage)
	if err != nil {
		message := fmt.Sprintf("invalid `page` parameter: %s", err)
		a.errorResponse(w, r, model.NewErrBadRequest(message))
		return
	}
	perPage, err := strconv.Atoi(strPerPage)
	if err != nil {
		message := fmt.Sprintf("invalid `per_page` parameter: %s", err)
		a.errorResponse(w, r, model.NewErrBadRequest(message))
		return
	}
	modifiedSince, err := strconv.ParseInt(strModifiedSince, 10, 64)
	if err != nil {
		message := fmt.Sprintf("invalid `modified_since` parameter: %s", err)
		a.errorResponse(w, r, model.NewErrBadRequest(message))
		return
	}

	opts := model.QueryBoardsComplianceHistoryOptions{
		ModifiedSince:  modifiedSince,
		IncludeDeleted: includeDeleted,
		TeamID:         teamID,
		Page:           page,
		PerPage:        perPage,
	}

	boards, more, err := a.app.GetBoardsComplianceHistory(opts)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	a.logger.Debug("GetBoardsComplianceHistory",
		mlog.String("teamID", teamID),
		mlog.Int("boardsCount", len(boards)),
		mlog.Bool("hasNext", more),
	)

	response := model.BoardsComplianceHistoryResponse{
		HasNext: more,
		Results: boards,
	}
	data, err := json.Marshal(response)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	jsonBytesResponse(w, http.StatusOK, data)
}

func (a *API) handleGetBlocksComplianceHistory(w http.ResponseWriter, r *http.Request) {
	// swagger:operation GET /admin/blocks_history getBlocksComplianceHistory
	//
	// Returns block histories for a specific team, specific board, or all teams and boards.
	//
	// Requires a license that includes Compliance feature. Caller must have `manage_system` permissions.
	//
	// ---
	// produces:
	// - application/json
	// parameters:
	// - name: modified_since
	//   in: query
	//   description: Filters for boards modified since timestamp; Unix time in milliseconds
	//   required: true
	//   type: integer
	// - name: include_deleted
	//   in: query
	//   description: When true then deleted boards are included. Default=false
	//   required: false
	//   type: boolean
	// - name: team_id
	//   in: query
	//   description: Team ID. If empty then block histories across all teams are included
	//   required: false
	//   type: string
	// - name: board_id
	//   in: query
	//   description: Board ID. If empty then block histories for all boards are included
	//   required: false
	//   type: string
	// - name: page
	//   in: query
	//   description: The page to select (default=0)
	//   required: false
	//   type: integer
	// - name: per_page
	//   in: query
	//   description: Number of block histories to return per page (default=60)
	//   required: false
	//   type: integer
	// security:
	// - BearerAuth: []
	// responses:
	//   '200':
	//     description: success
	//     schema:
	//       type: object
	//       items:
	//         "$ref": "#/definitions/BlocksComplianceHistoryResponse"
	//   default:
	//     description: internal error
	//     schema:
	//       "$ref": "#/definitions/ErrorResponse"

	query := r.URL.Query()
	strModifiedSince := query.Get("modified_since") // required, everything else optional
	includeDeleted := query.Get("include_deleted") == "true"
	strPage := query.Get("page")
	strPerPage := query.Get("per_page")
	teamID := query.Get("team_id")
	boardID := query.Get("board_id")

	if strModifiedSince == "" {
		a.errorResponse(w, r, model.NewErrBadRequest("`modified_since` parameter required"))
		return
	}

	// check for permission `manage_system`
	userID := getUserID(r)
	if !a.permissions.HasPermissionTo(userID, mm_model.PermissionManageSystem) {
		a.errorResponse(w, r, model.NewErrUnauthorized("access denied Compliance Export getBlocksHistory"))
		return
	}

	// check for valid license feature: compliance
	license := a.app.GetLicense()
	if license == nil || !(*license.Features.Compliance) {
		a.errorResponse(w, r, model.NewErrNotImplemented("insufficient license Compliance Export getBlocksHistory"))
		return
	}

	// check for valid team if specified
	if teamID != "" {
		_, err := a.app.GetTeam(teamID)
		if err != nil {
			a.errorResponse(w, r, model.NewErrBadRequest("invalid team id: "+teamID))
			return
		}
	}

	// check for valid board if specified
	if boardID != "" {
		_, err := a.app.GetBoard(boardID)
		if err != nil {
			a.errorResponse(w, r, model.NewErrBadRequest("invalid board id: "+boardID))
			return
		}
	}

	if strPage == "" {
		strPage = complianceDefaultPage
	}
	if strPerPage == "" {
		strPerPage = complianceDefaultPerPage
	}
	page, err := strconv.Atoi(strPage)
	if err != nil {
		message := fmt.Sprintf("invalid `page` parameter: %s", err)
		a.errorResponse(w, r, model.NewErrBadRequest(message))
		return
	}
	perPage, err := strconv.Atoi(strPerPage)
	if err != nil {
		message := fmt.Sprintf("invalid `per_page` parameter: %s", err)
		a.errorResponse(w, r, model.NewErrBadRequest(message))
		return
	}
	modifiedSince, err := strconv.ParseInt(strModifiedSince, 10, 64)
	if err != nil {
		message := fmt.Sprintf("invalid `modified_since` parameter: %s", err)
		a.errorResponse(w, r, model.NewErrBadRequest(message))
		return
	}

	opts := model.QueryBlocksComplianceHistoryOptions{
		ModifiedSince:  modifiedSince,
		IncludeDeleted: includeDeleted,
		TeamID:         teamID,
		BoardID:        boardID,
		Page:           page,
		PerPage:        perPage,
	}

	blocks, more, err := a.app.GetBlocksComplianceHistory(opts)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	a.logger.Debug("GetBlocksComplianceHistory",
		mlog.String("teamID", teamID),
		mlog.String("boardID", boardID),
		mlog.Int("blocksCount", len(blocks)),
		mlog.Bool("hasNext", more),
	)

	response := model.BlocksComplianceHistoryResponse{
		HasNext: more,
		Results: blocks,
	}
	data, err := json.Marshal(response)
	if err != nil {
		a.errorResponse(w, r, err)
		return
	}

	jsonBytesResponse(w, http.StatusOK, data)
}
