package main

import (
	"regexp"
	"strings"
	"testing"

	"github.com/anz-bank/sysl/pkg/loader"

	"github.com/anz-bank/sysl/pkg/sequencediagram"

	"github.com/anz-bank/sysl/pkg/cmdutils"

	"github.com/alecthomas/kingpin/v2"
	"github.com/anz-bank/sysl/pkg/syslutil"
	"github.com/sirupsen/logrus/hooks/test"
	"github.com/spf13/afero"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type loadAppArgs struct {
	root   string
	models string
}

func TestLoadAppReturnError(t *testing.T) {
	t.Parallel()

	args := loadAppArgs{
		"demo/simple/", "",
	}
	_, fs := syslutil.WriteToMemOverlayFs(args.root)
	logger, _ := test.NewNullLogger()
	_, _, err := loader.LoadSyslModule(args.root, args.models, fs, logger)
	assert.Error(t, err)
}

func TestLoadApp(t *testing.T) {
	t.Parallel()

	args := loadAppArgs{
		testDir, "sequence_diagram_test.sysl",
	}
	memFs, fs := syslutil.WriteToMemOverlayFs("/")
	logger, _ := test.NewNullLogger()
	mod, name, err := loader.LoadSyslModule(args.root, args.models, fs, logger)
	require.NoError(t, err)
	assert.NotNil(t, mod)
	syslutil.AssertFsHasExactly(t, memFs)
	apps := mod.GetApps()
	app := apps["Database"]

	assert.Equal(t, "Database", name)
	assert.Equal(t, []string{"Database"}, app.GetName().GetPart())

	appPatternsAttr := app.GetAttrs()["patterns"].GetA().GetElt()
	patterns := make([]string, 0, len(appPatternsAttr))
	for _, val := range appPatternsAttr {
		patterns = append(patterns, val.GetS())
	}
	assert.Equal(t, []string{"db"}, patterns)

	queryUserParams := app.GetEndpoints()["QueryUser"].GetParam()
	params := make([]string, 0, len(queryUserParams))
	for _, val := range queryUserParams {
		params = append(params, val.GetName())
	}
	assert.Equal(t, []string{"user_id"}, params)
}

type sdArgs struct {
	RootModel      string
	EndpointFormat string
	AppFormat      string
	Title          string
	Output         string
	Endpoints      []string
	Apps           []string
	Modules        string
	Blackboxes     [][]string
	Groupbox       string
}

func TestDoConstructSequenceDiagramsNoSyslSdFiltersWithoutEndpoints(t *testing.T) {
	t.Parallel()

	// Given
	args := &sdArgs{
		RootModel: testDir,
		Modules:   "sequence_diagram_test.sysl",
	}
	expected := make(map[string]string)

	// When
	result, err := DoConstructSequenceDiagramsWithParams(args.RootModel, args.EndpointFormat, args.AppFormat,
		args.Title, args.Output, args.Modules, args.Endpoints, args.Apps, args.Blackboxes,
		args.Groupbox)
	require.NoError(t, err)

	// Then
	assert.Equal(t, expected, result)
}

func TestDoConstructSequenceDiagramsMissingFile(t *testing.T) {
	t.Parallel()

	// Given
	args := &sdArgs{
		RootModel: testDir,
		Modules:   "MISSING_FILE.sysl",
	}

	// When
	_, err := DoConstructSequenceDiagramsWithParams(args.RootModel, args.EndpointFormat, args.AppFormat,
		args.Title, args.Output, args.Modules, args.Endpoints, args.Apps, args.Blackboxes,
		args.Groupbox)
	assert.Error(t, err)
}

func TestDoConstructSequenceDiagramsNoSyslSdFilters(t *testing.T) {
	t.Parallel()

	// Given
	args := &sdArgs{
		RootModel: testDir,
		Modules:   "sequence_diagram_test.sysl",
		Endpoints: []string{"QueryUser"},
		Output:    "_.png",
	}

	// When
	_, err := DoConstructSequenceDiagramsWithParams(args.RootModel, args.EndpointFormat, args.AppFormat,
		args.Title, args.Output, args.Modules, args.Endpoints, args.Apps, args.Blackboxes,
		args.Groupbox)

	// Then
	assert.Error(t, err)
}

func TestDoConstructSequenceDiagrams(t *testing.T) {
	t.Parallel()

	// Given
	args := &sdArgs{
		RootModel: testDir,
		Modules:   "sequence_diagram_project.sysl",
		Output:    "%(epname).png",
		Apps:      []string{"Project"},
	}
	expectContent := `''''''''''''''''''''''''''''''''''''''''''
''                                      ''
''  AUTOGENERATED CODE -- DO NOT EDIT!  ''
''                                      ''
''''''''''''''''''''''''''''''''''''''''''

@startuml
control "" as _0
control "" as _1
database "" as _2
skinparam maxMessageSize 250
title Profile
== WebFrontend <- RequestProfile ==
[->_0 : RequestProfile
activate _0
 _0->_1 :` + " " + `
 activate _1
  _1->_2 :` + " " + `
  activate _2
  _1<--_2 : User
  deactivate _2
 _0<--_1 : UserProfile
 deactivate _1
[<--_0 : Profile Page
deactivate _0
@enduml
`
	expected := map[string]string{
		"_.png": expectContent,
	}

	// When
	result, err := DoConstructSequenceDiagramsWithParams(args.RootModel, args.EndpointFormat, args.AppFormat,
		args.Title, args.Output, args.Modules, args.Endpoints, args.Apps, args.Blackboxes,
		args.Groupbox)
	require.NoError(t, err)

	// Then
	assert.Equal(t, expected, result)
}

func TestDoConstructSequenceDiagramWithBlackbox(t *testing.T) {
	t.Parallel()

	// Given
	args := &sdArgs{
		RootModel:  testDir,
		Modules:    "call.sysl",
		Output:     "tests/call.png",
		Endpoints:  []string{"MobileApp <- Login"},
		Blackboxes: [][]string{{"Server <- Login", "call to database"}},
	}

	// When
	expectContent := `''''''''''''''''''''''''''''''''''''''''''
''                                      ''
''  AUTOGENERATED CODE -- DO NOT EDIT!  ''
''                                      ''
''''''''''''''''''''''''''''''''''''''''''

@startuml
control "" as _0
control "" as _1
control "" as _2
skinparam maxMessageSize 250
== MobileApp <- Login ==
[->_0 : Login
activate _0
 _0->_1 :` + " " + `
 activate _1
  _1->_2 :` + " " + `
  activate _2
  note over _2: call to database
  _1<--_2 : <color blue>Server.LoginResponse</color> <<color green>?, ?</color>>
  deactivate _2
 _0<--_1 : <color blue>APIGateway.LoginResponse</color> <<color green>?, ?</color>>
 deactivate _1
deactivate _0
@enduml
`
	result, err := DoConstructSequenceDiagramsWithParams(args.RootModel, args.EndpointFormat, args.AppFormat,
		args.Title, args.Output, args.Modules, args.Endpoints, args.Apps, args.Blackboxes,
		args.Groupbox)
	require.NoError(t, err)
	expected := map[string]string{"tests/call.png": expectContent}
	// Then
	assert.Equal(t, expected, result)
}

func TestDoConstructSequenceDiagramsToFormatComplexName(t *testing.T) {
	t.Parallel()

	// Given
	args := &sdArgs{
		RootModel: testDir,
		Modules:   "sequence_diagram_complex_format.sysl",
		Output:    "%(epname).png",
		Apps:      []string{"Project"},
	}
	expectContent := `''''''''''''''''''''''''''''''''''''''''''
''                                      ''
''  AUTOGENERATED CODE -- DO NOT EDIT!  ''
''                                      ''
''''''''''''''''''''''''''''''''''''''''''

@startuml
control "//te//\n<color grey>Ex e</color>\n**User**" as _0
control "**MobileApp**" as _1
skinparam maxMessageSize 250
title Diagram
== User <- Check Balance ==
[->_0 : Check Balance
activate _0
 _0->_1 : //«hello»//** <color red>pat?</color>**aa Login
 deactivate _0
@enduml
`
	// When
	result, err := DoConstructSequenceDiagramsWithParams(args.RootModel, args.EndpointFormat, args.AppFormat,
		args.Title, args.Output, args.Modules, args.Endpoints, args.Apps, args.Blackboxes,
		args.Groupbox)
	require.NoError(t, err)

	// Then
	assert.Equal(t, map[string]string{"Seq.png": expectContent}, result)
}

func TestDosequencediagram(t *testing.T) {
	t.Parallel()

	args := &sdArgs{
		Modules: "sequence_diagram_complex_format.sysl",
		Output:  "%(epname).png",
		Apps:    []string{"Project"},
	}
	argsData := []string{"sysl", "sd", "-o", args.Output, "-a", args.Apps[0], args.Modules}
	sysl := kingpin.New("sysl", "System Modelling Language Toolkit")
	r := cmdRunner{}
	assert.NoError(t, r.Configure(sysl))
	selectedCommand, err := sysl.Parse(argsData[1:])
	assert.Nil(t, err, "Cmd line parse failed for sysl sd")
	assert.Equal(t, "sd", selectedCommand)
}

func TestDoConstructSequenceDiagramsWithParams(t *testing.T) {

}

func TestDoConstructSequenceDiagramWithGroupingCommandline(t *testing.T) {
	t.Parallel()

	// Given
	args := &sdArgs{
		RootModel: testDir,
		Modules:   "call.sysl",
		Output:    "tests/call.png",
		Endpoints: []string{"MobileApp <- Login"},
		Groupbox:  "owner",
	}
	var boxPresent bool
	var err error

	// When
	boxServer := `box "server" #LightBlue
	participant _\d
	participant _\d
end box`
	boxClient := `box "client" #LightBlue
	participant _\d
	participant _\d
end box`
	result, err := DoConstructSequenceDiagramsWithParams(args.RootModel, args.EndpointFormat, args.AppFormat,
		args.Title, args.Output, args.Modules, args.Endpoints, args.Apps, args.Blackboxes,
		args.Groupbox)
	require.NoError(t, err)

	// Then
	boxPresent, err = regexp.MatchString(boxServer, result["tests/call.png"])
	assert.Nil(t, err, "Error compiling regular expression")
	assert.True(t, boxPresent)
	boxPresent, err = regexp.MatchString(boxClient, result["tests/call.png"])
	assert.Nil(t, err, "Error compiling regular expression")
	assert.True(t, boxPresent)
	assert.Equal(t, 4, strings.Count(result["tests/call.png"], "participant"))
}

func TestDoConstructSequenceDiagramWithGroupingSysl(t *testing.T) {
	t.Parallel()

	// Given
	args := &sdArgs{
		RootModel: testDir,
		Modules:   "groupby.sysl",
		Output:    "%(epname).png",
		Endpoints: []string{"Project :: Sequences <- SEQ-One"},
		Apps:      []string{},
	}
	var boxPresent bool
	var err error

	// When
	boxOnpremise := `box "onpremise" #LightBlue
	participant _\d
	participant _\d
end box`
	boxCloud := `box "cloud" #LightBlue
	participant _\d
	participant _\d
end box`
	result, err := DoConstructSequenceDiagramsWithParams(args.RootModel, args.EndpointFormat, args.AppFormat,
		args.Title, args.Output, args.Modules, args.Endpoints, args.Apps, args.Blackboxes,
		args.Groupbox)
	require.NoError(t, err)

	// Then
	boxPresent, err = regexp.MatchString(boxOnpremise, result["SEQ-One.png"])
	assert.Nil(t, err, "Error compiling regular expression")
	assert.True(t, boxPresent)
	boxPresent, err = regexp.MatchString(boxCloud, result["SEQ-One.png"])
	assert.Nil(t, err, "Error compiling regular expression")
	assert.True(t, boxPresent)
}

func TestDoConstructSequenceDiagramWithOneEntityBox(t *testing.T) {
	t.Parallel()

	// Given
	args := &sdArgs{
		RootModel: testDir,
		Modules:   "groupby.sysl",
		Output:    "%(epname).png",
		Endpoints: []string{"Project :: Sequences <- SEQ-Two"},
		Apps:      []string{},
		Groupbox:  "location",
	}

	var boxPresent bool
	var err error

	// When
	boxCloud := `box "cloud" #LightBlue
	participant _\d
end box`
	result, err := DoConstructSequenceDiagramsWithParams(args.RootModel, args.EndpointFormat, args.AppFormat,
		args.Title, args.Output, args.Modules, args.Endpoints, args.Apps, args.Blackboxes,
		args.Groupbox)
	require.NoError(t, err)

	// Then
	boxPresent, err = regexp.MatchString(boxCloud, result["SEQ-Two.png"])
	assert.Nil(t, err, "Error compiling regular expression")
	assert.True(t, boxPresent)
}

func DoConstructSequenceDiagramsWithParams(
	rootModel, endpointFormat, appFormat, title, output, modules string,
	endpoints, apps []string,
	blackboxes [][]string,
	group string,
) (map[string]string, error) {
	logger, _ := test.NewNullLogger()
	mod, _, err := loader.LoadSyslModule(rootModel, modules, afero.NewOsFs(), logger)
	if err != nil {
		return nil, err
	}
	cmdContextParamSeqgen := &cmdutils.CmdContextParamSeqgen{
		EndpointFormat: endpointFormat,
		AppFormat:      appFormat,
		Title:          title,
		Output:         output,
		EndpointsFlag:  endpoints,
		AppsFlag:       apps,
		Blackboxes:     blackboxes,
		Group:          group,
	}
	return sequencediagram.DoConstructSequenceDiagrams(cmdContextParamSeqgen, mod, logger)
}
