package crontab

import (
	"bytes"
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

var parseCrontabTestCases = []struct {
	crontab  string
	expected *Crontab
}{
	// Success cases
	{
		"",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{},
				Timezone: time.Local,
			},
			Jobs: []*Job{},
		},
	},

	{
		"\n",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{},
				Timezone: time.Local,
			},
			Jobs: []*Job{},
		},
	},

	{
		"FOO=bar\n",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{"FOO": "bar"},
				Timezone: time.Local,
			},
			Jobs: []*Job{},
		},
	},

	{
		"FOO=bar",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{"FOO": "bar"},
				Timezone: time.Local,
			},
			Jobs: []*Job{},
		},
	},

	{
		"FOO=\"bar\"",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{"FOO": "bar"},
				Timezone: time.Local,
			},
			Jobs: []*Job{},
		},
	},

	{
		"FOO='bar'",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{"FOO": "bar"},
				Timezone: time.Local,
			},
			Jobs: []*Job{},
		},
	},

	{
		"FOO='",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{"FOO": "'"},
				Timezone: time.Local,
			},
			Jobs: []*Job{},
		},
	},

	{
		"FOO=''",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{"FOO": ""},
				Timezone: time.Local,
			},
			Jobs: []*Job{},
		},
	},

	{
		"CRON_TZ=UTC",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{"CRON_TZ": "UTC"},
				Timezone: time.UTC,
			},
			Jobs: []*Job{},
		},
	},

	{
		"* * * * * foo some # qux",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{},
				Timezone: time.Local,
			},
			Jobs: []*Job{
				{
					CrontabLine: CrontabLine{
						Schedule: "* * * * *",
						Command:  "foo some # qux",
					},
				},
			},
		},
	},

	{
		"* * * * * foo\nSHELL=some\n1 1 1 1 1 bar\nKEY=VAL",
		&Crontab{
			Context: &Context{
				Shell: "some",
				Environ: map[string]string{
					"SHELL": "some",
					"KEY":   "VAL",
				},
				Timezone: time.Local,
			},
			Jobs: []*Job{
				{
					CrontabLine: CrontabLine{
						Schedule: "* * * * *",
						Command:  "foo",
					},
				},
				{
					CrontabLine: CrontabLine{
						Schedule: "1 1 1 1 1",
						Command:  "bar",
					},
				},
			},
		},
	},

	{
		"* * * * * * with year\n* * * * * * * with seconds\n@daily with shorthand",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{},
				Timezone: time.Local,
			},
			Jobs: []*Job{
				{
					CrontabLine: CrontabLine{
						Schedule: "* * * * * *",
						Command:  "with year",
					},
				},
				{
					CrontabLine: CrontabLine{
						Schedule: "* * * * * * *",
						Command:  "with seconds",
					},
				},
				{
					CrontabLine: CrontabLine{
						Schedule: "@daily",
						Command:  "with shorthand",
					},
				},
			},
		},
	},

	{
		"# * * * * * * commented\n\n\n  # some\n\t\n\t# more\n  \t  */2 * * * * will run",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{},
				Timezone: time.Local,
			},
			Jobs: []*Job{
				{
					CrontabLine: CrontabLine{
						Schedule: "*/2 * * * *",
						Command:  "will run",
					},
				},
			},
		},
	},

	{
		"* * * * *        \twith plenty of whitespace",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{},
				Timezone: time.Local,
			},
			Jobs: []*Job{
				{
					CrontabLine: CrontabLine{
						Schedule: "* * * * *",
						Command:  "with plenty of whitespace",
					},
				},
			},
		},
	},

	{
		"*\t*\t*\t*\t*\ttabs everywhere\n",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{},
				Timezone: time.Local,
			},
			Jobs: []*Job{
				{
					CrontabLine: CrontabLine{
						Schedule: "*\t*\t*\t*\t*",
						Command:  "tabs everywhere",
					},
				},
			},
		},
	},

	{
		"@hourly foo1 foo2 foo3 foo4 foo5 foo6",
		&Crontab{
			Context: &Context{
				Shell:    "/bin/sh",
				Environ:  map[string]string{},
				Timezone: time.Local,
			},
			Jobs: []*Job{
				{
					CrontabLine: CrontabLine{
						Schedule: "@hourly",
						Command:  "foo1 foo2 foo3 foo4 foo5 foo6",
					},
				},
			},
		},
	},

	// Failure cases
	{"* foo \n", nil},
	{"* some * * *  more\n", nil},
	{"* some * * *  \n", nil},
	{"FOO\n", nil},
}

func TestParseCrontab(t *testing.T) {
	for _, tt := range parseCrontabTestCases {
		label := fmt.Sprintf("ParseCrontab(%q)", tt.crontab)

		reader := bytes.NewBufferString(tt.crontab)

		crontab, err := ParseCrontab(reader)

		if tt.expected == nil {
			assert.Nil(t, crontab, label)
			assert.NotNil(t, err, label)
		} else {
			if assert.NotNil(t, crontab, label) {
				assert.Equal(t, tt.expected.Context, crontab.Context, label)

				if assert.Equal(t, len(tt.expected.Jobs), len(crontab.Jobs), label) {
					for i, crontabJob := range crontab.Jobs {
						expectedJob := tt.expected.Jobs[i]
						assert.Equal(t, expectedJob.Command, crontabJob.Command, label)
						assert.Equal(t, expectedJob.Schedule, crontabJob.Schedule, label)
						assert.NotNil(t, crontabJob.Expression, label)
					}
				}
			}
		}
	}
}
