package fakedata

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/brianvoe/gofakeit/v7"
	"github.com/pocketbase/pocketbase/core"
	"github.com/pocketbase/pocketbase/daos"
	"github.com/pocketbase/pocketbase/models"
	"github.com/pocketbase/pocketbase/tools/security"

	"github.com/SecurityBrewery/catalyst/migrations"
)

const (
	minimumUserCount   = 1
	minimumTicketCount = 1
)

func Generate(app core.App, userCount, ticketCount int) error {
	if userCount < minimumUserCount {
		userCount = minimumUserCount
	}

	if ticketCount < minimumTicketCount {
		ticketCount = minimumTicketCount
	}

	records, err := Records(app, userCount, ticketCount)
	if err != nil {
		return err
	}

	for _, record := range records {
		if err := app.Dao().SaveRecord(record); err != nil {
			return err
		}
	}

	return nil
}

func Records(app core.App, userCount int, ticketCount int) ([]*models.Record, error) {
	types, err := app.Dao().FindRecordsByExpr(migrations.TypeCollectionName)
	if err != nil {
		return nil, err
	}

	users := userRecords(app.Dao(), userCount)
	tickets := ticketRecords(app.Dao(), users, types, ticketCount)
	reactions := reactionRecords(app.Dao())

	var records []*models.Record
	records = append(records, users...)
	records = append(records, types...)
	records = append(records, tickets...)
	records = append(records, reactions...)

	return records, nil
}

func userRecords(dao *daos.Dao, count int) []*models.Record {
	collection, err := dao.FindCollectionByNameOrId(migrations.UserCollectionName)
	if err != nil {
		panic(err)
	}

	records := make([]*models.Record, 0, count)

	// create the test user
	if _, err := dao.FindRecordById(migrations.UserCollectionName, "u_test"); err != nil {
		record := models.NewRecord(collection)
		record.SetId("u_test")
		_ = record.SetUsername("u_test")
		_ = record.SetPassword("1234567890")
		record.Set("name", gofakeit.Name())
		record.Set("email", "user@catalyst-soar.com")
		_ = record.SetVerified(true)

		records = append(records, record)
	}

	for range count - 1 {
		record := models.NewRecord(collection)
		record.SetId("u_" + security.PseudorandomString(10))
		_ = record.SetUsername("u_" + security.RandomStringWithAlphabet(5, "123456789"))
		_ = record.SetPassword("1234567890")
		record.Set("name", gofakeit.Name())
		record.Set("email", gofakeit.Username()+"@catalyst-soar.com")
		_ = record.SetVerified(true)

		records = append(records, record)
	}

	return records
}

func ticketRecords(dao *daos.Dao, users, types []*models.Record, count int) []*models.Record {
	collection, err := dao.FindCollectionByNameOrId(migrations.TicketCollectionName)
	if err != nil {
		panic(err)
	}

	records := make([]*models.Record, 0, count)

	created := time.Now()
	number := gofakeit.Number(200*count, 300*count)

	for range count {
		number -= gofakeit.Number(100, 200)
		created = created.Add(time.Duration(-gofakeit.Number(13, 37)) * time.Hour)

		record := models.NewRecord(collection)
		record.SetId("t_" + security.PseudorandomString(10))

		updated := gofakeit.DateRange(created, time.Now())

		ticketType := random(types)

		record.Set("created", created.Format("2006-01-02T15:04:05Z"))
		record.Set("updated", updated.Format("2006-01-02T15:04:05Z"))

		record.Set("name", fmt.Sprintf("%s-%d", strings.ToUpper(ticketType.GetString("singular")), number))
		record.Set("type", ticketType.GetId())
		record.Set("description", fakeTicketDescription())
		record.Set("open", gofakeit.Bool())
		record.Set("schema", `{"type":"object","properties":{"tlp":{"title":"TLP","type":"string"}}}`)
		record.Set("state", `{"severity":"Medium"}`)
		record.Set("owner", random(users).GetId())

		records = append(records, record)

		// Add comments
		records = append(records, commentRecords(dao, users, created, record)...)
		records = append(records, timelineRecords(dao, created, record)...)
		records = append(records, taskRecords(dao, users, created, record)...)
		records = append(records, linkRecords(dao, created, record)...)
	}

	return records
}

func commentRecords(dao *daos.Dao, users []*models.Record, created time.Time, record *models.Record) []*models.Record {
	commentCollection, err := dao.FindCollectionByNameOrId(migrations.CommentCollectionName)
	if err != nil {
		panic(err)
	}

	records := make([]*models.Record, 0, 5)

	for range gofakeit.IntN(5) {
		commentCreated := gofakeit.DateRange(created, time.Now())
		commentUpdated := gofakeit.DateRange(commentCreated, time.Now())

		commentRecord := models.NewRecord(commentCollection)
		commentRecord.SetId("c_" + security.PseudorandomString(10))
		commentRecord.Set("created", commentCreated.Format("2006-01-02T15:04:05Z"))
		commentRecord.Set("updated", commentUpdated.Format("2006-01-02T15:04:05Z"))
		commentRecord.Set("ticket", record.GetId())
		commentRecord.Set("author", random(users).GetId())
		commentRecord.Set("message", fakeTicketComment())

		records = append(records, commentRecord)
	}

	return records
}

func timelineRecords(dao *daos.Dao, created time.Time, record *models.Record) []*models.Record {
	timelineCollection, err := dao.FindCollectionByNameOrId(migrations.TimelineCollectionName)
	if err != nil {
		panic(err)
	}

	records := make([]*models.Record, 0, 5)

	for range gofakeit.IntN(5) {
		timelineCreated := gofakeit.DateRange(created, time.Now())
		timelineUpdated := gofakeit.DateRange(timelineCreated, time.Now())

		timelineRecord := models.NewRecord(timelineCollection)
		timelineRecord.SetId("tl_" + security.PseudorandomString(10))
		timelineRecord.Set("created", timelineCreated.Format("2006-01-02T15:04:05Z"))
		timelineRecord.Set("updated", timelineUpdated.Format("2006-01-02T15:04:05Z"))
		timelineRecord.Set("ticket", record.GetId())
		timelineRecord.Set("time", gofakeit.DateRange(created, time.Now()).Format("2006-01-02T15:04:05Z"))
		timelineRecord.Set("message", fakeTicketTimelineMessage())

		records = append(records, timelineRecord)
	}

	return records
}

func taskRecords(dao *daos.Dao, users []*models.Record, created time.Time, record *models.Record) []*models.Record {
	taskCollection, err := dao.FindCollectionByNameOrId(migrations.TaskCollectionName)
	if err != nil {
		panic(err)
	}

	records := make([]*models.Record, 0, 5)

	for range gofakeit.IntN(5) {
		taskCreated := gofakeit.DateRange(created, time.Now())
		taskUpdated := gofakeit.DateRange(taskCreated, time.Now())

		taskRecord := models.NewRecord(taskCollection)
		taskRecord.SetId("ts_" + security.PseudorandomString(10))
		taskRecord.Set("created", taskCreated.Format("2006-01-02T15:04:05Z"))
		taskRecord.Set("updated", taskUpdated.Format("2006-01-02T15:04:05Z"))
		taskRecord.Set("ticket", record.GetId())
		taskRecord.Set("name", fakeTicketTask())
		taskRecord.Set("open", gofakeit.Bool())
		taskRecord.Set("owner", random(users).GetId())

		records = append(records, taskRecord)
	}

	return records
}

func linkRecords(dao *daos.Dao, created time.Time, record *models.Record) []*models.Record {
	linkCollection, err := dao.FindCollectionByNameOrId(migrations.LinkCollectionName)
	if err != nil {
		panic(err)
	}

	records := make([]*models.Record, 0, 5)

	for range gofakeit.IntN(5) {
		linkCreated := gofakeit.DateRange(created, time.Now())
		linkUpdated := gofakeit.DateRange(linkCreated, time.Now())

		linkRecord := models.NewRecord(linkCollection)
		linkRecord.SetId("l_" + security.PseudorandomString(10))
		linkRecord.Set("created", linkCreated.Format("2006-01-02T15:04:05Z"))
		linkRecord.Set("updated", linkUpdated.Format("2006-01-02T15:04:05Z"))
		linkRecord.Set("ticket", record.GetId())
		linkRecord.Set("url", gofakeit.URL())
		linkRecord.Set("name", random([]string{"Blog", "Forum", "Wiki", "Documentation"}))

		records = append(records, linkRecord)
	}

	return records
}

const createTicketPy = `import sys
import json
import random
import os

from pocketbase import PocketBase

# Connect to the PocketBase server
client = PocketBase(os.environ["CATALYST_APP_URL"])
client.auth_store.save(token=os.environ["CATALYST_TOKEN"])

newtickets = client.collection("tickets").get_list(1, 200, {"filter": 'name = "New Ticket"'})
for ticket in newtickets.items:
	client.collection("tickets").delete(ticket.id)

# Create a new ticket
client.collection("tickets").create({
	"name": "New Ticket",
	"type": "alert",
	"open": True,
})`

const alertIngestPy = `import sys
import json
import random
import os

from pocketbase import PocketBase

# Parse the event from the webhook payload
event = json.loads(sys.argv[1])
body = json.loads(event["body"])

# Connect to the PocketBase server
client = PocketBase(os.environ["CATALYST_APP_URL"])
client.auth_store.save(token=os.environ["CATALYST_TOKEN"])

# Create a new ticket
client.collection("tickets").create({
	"name": body["name"],
	"type": "alert",
	"open": True,
})`

const assignTicketsPy = `import sys
import json
import random
import os

from pocketbase import PocketBase

# Parse the ticket from the input
ticket = json.loads(sys.argv[1])

# Connect to the PocketBase server
client = PocketBase(os.environ["CATALYST_APP_URL"])
client.auth_store.save(token=os.environ["CATALYST_TOKEN"])

# Get a random user
users = client.collection("users").get_list(1, 200)
random_user = random.choice(users.items)

# Assign the ticket to the random user
client.collection("tickets").update(ticket["record"]["id"], {
	"owner": random_user.id,
})`

const (
	triggerSchedule = `{"expression":"12 * * * *"}`
	triggerWebhook  = `{"token":"1234567890","path":"webhook"}`
	triggerHook     = `{"collections":["tickets"],"events":["create"]}`
)

func reactionRecords(dao *daos.Dao) []*models.Record {
	var records []*models.Record

	collection, err := dao.FindCollectionByNameOrId(migrations.ReactionCollectionName)
	if err != nil {
		panic(err)
	}

	createTicketActionData, err := json.Marshal(map[string]interface{}{
		"requirements": "pocketbase",
		"script":       createTicketPy,
	})
	if err != nil {
		panic(err)
	}

	record := models.NewRecord(collection)
	record.SetId("w_" + security.PseudorandomString(10))
	record.Set("name", "Create New Ticket")
	record.Set("trigger", "schedule")
	record.Set("triggerdata", triggerSchedule)
	record.Set("action", "python")
	record.Set("actiondata", string(createTicketActionData))

	records = append(records, record)

	alertIngestActionData, err := json.Marshal(map[string]interface{}{
		"requirements": "pocketbase",
		"script":       alertIngestPy,
	})
	if err != nil {
		panic(err)
	}

	record = models.NewRecord(collection)
	record.SetId("w_" + security.PseudorandomString(10))
	record.Set("name", "Alert Ingest Webhook")
	record.Set("trigger", "webhook")
	record.Set("triggerdata", triggerWebhook)
	record.Set("action", "python")
	record.Set("actiondata", string(alertIngestActionData))

	records = append(records, record)

	assignTicketsActionData, err := json.Marshal(map[string]interface{}{
		"requirements": "pocketbase",
		"script":       assignTicketsPy,
	})
	if err != nil {
		panic(err)
	}

	record = models.NewRecord(collection)
	record.SetId("w_" + security.PseudorandomString(10))
	record.Set("name", "Assign new Tickets")
	record.Set("trigger", "hook")
	record.Set("triggerdata", triggerHook)
	record.Set("action", "python")
	record.Set("actiondata", string(assignTicketsActionData))

	records = append(records, record)

	return records
}
