package service

import (
	"context"
	"strings"

	commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
	"gorm.io/gorm"
)

type ApprovalService interface {
	RegisterWorkflowSteps(workflow approvalutils.ApprovalWorkflowKey, steps map[approvalutils.ApprovalStep]string) error
	WorkflowSteps(workflow approvalutils.ApprovalWorkflowKey) map[approvalutils.ApprovalStep]string
	WorkflowStepName(workflow approvalutils.ApprovalWorkflowKey, step approvalutils.ApprovalStep) (string, bool)
	CreateApproval(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, step approvalutils.ApprovalStep, action *entity.ApprovalAction, actorID uint, note *string) (*entity.Approval, error)
	List(ctx context.Context, module string, approvableID *uint, page, limit int, search string, orderByDate string) ([]entity.Approval, int64, error)
	ListByTarget(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, modifier func(*gorm.DB) *gorm.DB) ([]entity.Approval, error)
	LatestByTarget(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, modifier func(*gorm.DB) *gorm.DB) (*entity.Approval, error)
	LatestByTargets(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableIDs []uint, modifier func(*gorm.DB) *gorm.DB) (map[uint]*entity.Approval, error)
}

type approvalService struct {
	repo commonRepo.ApprovalRepository
}

func NewApprovalService(repo commonRepo.ApprovalRepository) ApprovalService {
	return &approvalService{repo: repo}
}

func (s *approvalService) RegisterWorkflowSteps(workflow approvalutils.ApprovalWorkflowKey, steps map[approvalutils.ApprovalStep]string) error {
	return approvalutils.RegisterWorkflowSteps(workflow, steps)
}

func (s *approvalService) WorkflowSteps(workflow approvalutils.ApprovalWorkflowKey) map[approvalutils.ApprovalStep]string {
	return approvalutils.WorkflowSteps(workflow)
}

func (s *approvalService) WorkflowStepName(workflow approvalutils.ApprovalWorkflowKey, step approvalutils.ApprovalStep) (string, bool) {
	return approvalutils.ApprovalStepName(workflow, step)
}

func (s *approvalService) CreateApproval(
	ctx context.Context,
	workflow approvalutils.ApprovalWorkflowKey,
	approvableID uint,
	step approvalutils.ApprovalStep,
	action *entity.ApprovalAction,
	actorID uint,
	note *string,
) (*entity.Approval, error) {
	record, err := approvalutils.NewApproval(workflow, approvableID, step, action, actorID, note)
	if err != nil {
		return nil, err
	}

	if err := s.repo.CreateOne(ctx, record, nil); err != nil {
		return nil, err
	}

	s.decorateApproval(workflow, record)

	return record, nil
}

func (s *approvalService) List(
	ctx context.Context,
	module string,
	approvableID *uint,
	page, limit int,
	search string,
	orderByDate string,
) ([]entity.Approval, int64, error) {
	module = strings.TrimSpace(strings.ToUpper(module))
	search = strings.TrimSpace(search)
	orderByDate = strings.TrimSpace(strings.ToUpper(orderByDate))
	if orderByDate != "ASC" && orderByDate != "DESC" {
		orderByDate = "DESC"
	}

	if limit <= 0 {
		limit = 10
	}
	if page <= 0 {
		page = 1
	}

	offset := (page - 1) * limit

	records, total, err := s.repo.GetAll(
		ctx,
		offset,
		limit,
		func(db *gorm.DB) *gorm.DB {
			query := db.
				Where("approvable_type = ?", module).
				Order("action_at " + orderByDate).
				Preload("ActionUser")

			if approvableID != nil {
				query = query.Where("approvable_id = ?", *approvableID)
			}

			if search != "" {
				like := "%" + strings.ToLower(search) + "%"
				query = query.Where("(LOWER(step_name) LIKE ? OR LOWER(action) LIKE ? OR LOWER(notes) LIKE ?)", like, like, like)
			}

			return query
		},
	)
	if err != nil {
		if s.isApprovalTableMissing(err) {
			return nil, 0, nil
		}
		return nil, 0, err
	}
	if len(records) == 0 {
		return nil, total, nil
	}

	workflow := approvalutils.ApprovalWorkflowKey(module)
	for i := range records {
		s.decorateApproval(workflow, &records[i])
	}

	return records, total, nil
}

func (s *approvalService) ListByTarget(
	ctx context.Context,
	workflow approvalutils.ApprovalWorkflowKey,
	approvableID uint,
	modifier func(*gorm.DB) *gorm.DB,
) ([]entity.Approval, error) {
	records, err := s.repo.FindByTarget(ctx, workflow.String(), approvableID, modifier)
	if err != nil {
		if s.isApprovalTableMissing(err) {
			return nil, nil
		}
		return nil, err
	}

	for i := range records {
		s.decorateApproval(workflow, &records[i])
	}

	return records, nil
}

func (s *approvalService) LatestByTarget(
	ctx context.Context,
	workflow approvalutils.ApprovalWorkflowKey,
	approvableID uint,
	modifier func(*gorm.DB) *gorm.DB,
) (*entity.Approval, error) {
	record, err := s.repo.LatestByTarget(ctx, workflow.String(), approvableID, modifier)
	if err != nil {
		if s.isApprovalTableMissing(err) {
			return nil, nil
		}
		return nil, err
	}
	if record == nil {
		return nil, nil
	}
	s.decorateApproval(workflow, record)
	return record, nil
}

func (s *approvalService) LatestByTargets(
	ctx context.Context,
	workflow approvalutils.ApprovalWorkflowKey,
	approvableIDs []uint,
	modifier func(*gorm.DB) *gorm.DB,
) (map[uint]*entity.Approval, error) {
	records, err := s.repo.LatestByTargets(ctx, workflow.String(), approvableIDs, modifier)
	if err != nil {
		if s.isApprovalTableMissing(err) {
			return nil, nil
		}
		return nil, err
	}
	if len(records) == 0 {
		return nil, nil
	}

	result := make(map[uint]*entity.Approval, len(records))
	for approvableID, approval := range records {
		approvalCopy := approval
		s.decorateApproval(workflow, &approvalCopy)
		result[approvableID] = &approvalCopy
	}

	return result, nil
}

func (s *approvalService) decorateApproval(workflow approvalutils.ApprovalWorkflowKey, approval *entity.Approval) {
	if approval == nil {
		return
	}
	currentName := strings.TrimSpace(approval.StepName)
	if currentName == "" {
		if name, ok := approvalutils.ApprovalStepName(workflow, approvalutils.ApprovalStep(approval.StepNumber)); ok {
			approval.StepName = name
		}
	} else {
		approval.StepName = currentName
	}
}

func (s *approvalService) isApprovalTableMissing(err error) bool {
	if err == nil {
		return false
	}

	errMsg := strings.ToLower(err.Error())

	if strings.Contains(errMsg, "no such table: approvals") {
		return true
	}

	schemaIssues := []string{
		`relation "approvals" does not exist`,
		`column "step_name" does not exist`,
		`column "step_number" does not exist`,
		`column "action" does not exist`,
		`column "status" does not exist`,
		`column "step" does not exist`,
	}
	for _, issue := range schemaIssues {
		if strings.Contains(errMsg, issue) {
			return true
		}
	}

	return false
}
