package database

import (
	"errors"
	"fmt"
	"log"
	"sort"

	"github.com/SecurityBrewery/catalyst/caql"
	"github.com/SecurityBrewery/catalyst/dag"
	"github.com/SecurityBrewery/catalyst/generated/model"
)

func playbookGraph(playbook *model.Playbook) (*dag.Graph, error) {
	d := dag.NewGraph()

	var taskIDs []string
	for taskID := range playbook.Tasks {
		taskIDs = append(taskIDs, taskID)
	}
	sort.Strings(taskIDs)

	for _, taskID := range taskIDs {
		if err := d.AddNode(taskID); err != nil {
			return nil, errors.New("could not add node")
		}
	}
	for _, taskID := range taskIDs {
		task := playbook.Tasks[taskID]
		for next := range task.Next {
			if err := d.AddEdge(taskID, next); err != nil {
				return nil, errors.New("could not add edge")
			}
		}
	}

	return d, nil
}

func toTaskResponse(playbook *model.Playbook, taskID string, order int, graph *dag.Graph) (*model.TaskResponse, error) {
	task, ok := playbook.Tasks[taskID]
	if !ok {
		return nil, fmt.Errorf("task %s not found", taskID)
	}

	tr := &model.TaskResponse{
		Automation: task.Automation,
		Closed:     task.Closed,
		Created:    task.Created,
		Data:       task.Data,
		Done:       task.Done,
		Join:       task.Join,
		Payload:    task.Payload,
		Name:       task.Name,
		Next:       task.Next,
		Owner:      task.Owner,
		Schema:     task.Schema,
		Type:       task.Type,
		// Active:     active,
		// Order:      v.Order,
	}

	tr.Order = int64(order)

	taskActive, _ := active(playbook, taskID, graph, task)
	tr.Active = taskActive

	return tr, nil
}

func activePlaybook(playbook *model.Playbook, taskID string) (bool, error) {
	task, ok := playbook.Tasks[taskID]
	if !ok {
		return false, fmt.Errorf("playbook does not contain tasks %s", taskID)
	}

	d, err := playbookGraph(playbook)
	if err != nil {
		return false, err
	}

	return active(playbook, taskID, d, task)
}

func active(playbook *model.Playbook, taskID string, d *dag.Graph, task *model.Task) (bool, error) {
	if task.Done {
		return false, nil
	}

	parents := d.GetParents(taskID)

	if len(parents) == 0 {
		return true, nil // valid(&task)
	}

	if task.Join != nil && *task.Join {
		for _, parent := range parents {
			parentTask := playbook.Tasks[parent]
			if !parentTask.Done {
				return false, nil
			}
			requirement := parentTask.Next[taskID]

			b, err := evalRequirement(requirement, parentTask.Data)
			if err != nil {
				return false, err
			}

			if !b {
				return false, nil
			}
		}

		return true, nil
	}

	for _, parent := range parents {
		parentTask := playbook.Tasks[parent]
		if !parentTask.Done {
			// return false, nil
			continue
		}
		requirement := parentTask.Next[taskID]

		b, err := evalRequirement(requirement, parentTask.Data)
		if err != nil {
			continue
		}

		if b {
			return true, nil
		}
	}

	return false, nil
}

func evalRequirement(aql string, data any) (bool, error) {
	if aql == "" {
		return true, nil
	}

	parser := caql.Parser{}
	tree, err := parser.Parse(aql)
	if err != nil {
		return false, err
	}

	var dataMap map[string]any
	if data != nil {
		if dataMapX, ok := data.(map[string]any); ok {
			dataMap = dataMapX
		} else {
			log.Println("wrong data type for task data")
		}
	}

	v, err := tree.Eval(dataMap)
	if err != nil {
		return false, err
	}

	if b, ok := v.(bool); ok {
		return b, nil
	}

	return false, err
}

/*
// "github.com/qri-io/jsonschema"
func valid(task *model.Task) (bool, error) {
	schema, err := json.Marshal(task.Schema)
	if err != nil {
		return false, err
	}

	rs := &jsonschema.Schema{}
	if err := json.Unmarshal(schema, rs); err != nil {
		return false, err
	}

	state := rs.Validate(context.Background(), task.Data)
	return len(*state.Errs) > 0, nil
}
*/
