package model

import (
	"errors"
	"hamster-tycoon/randomizer"
	"reflect"
	"testing"
)

func TestHamster_Die(t *testing.T) {
	testCases := []struct {
		caseName      string
		hamster       *Hamster
		expectedAlive bool
	}{
		{
			caseName: "Should die",
			hamster: &Hamster{
				Alive: true,
			},
			expectedAlive: false,
		},
		{
			caseName: "Already die",
			hamster: &Hamster{
				Alive: false,
			},
			expectedAlive: false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.caseName, func(t *testing.T) {
			tc.hamster.Die()
			if tc.hamster.Alive != tc.expectedAlive {
				t.Errorf("Die result does not match expectation. \n Got : %t \n Get : %t", tc.expectedAlive, tc.hamster.Alive)
			}
		})
	}
}

func TestHamster_Grow(t *testing.T) {
	testCases := []struct {
		caseName        string
		hamster         *Hamster
		expectedHamster *Hamster
	}{
		{
			caseName: "Age increment",
			hamster: &Hamster{
				Age:   0,
				Alive: true,
			},
			expectedHamster: &Hamster{
				Age:   1,
				Alive: true,
			},
		},
		{
			caseName: "Gestation decrement",
			hamster: &Hamster{
				Age:             0,
				Gestation:       true,
				GestationPeriod: 1,
				Alive:           true,
			},
			expectedHamster: &Hamster{
				Age:             1,
				Gestation:       true,
				GestationPeriod: 2,
				Alive:           true,
			},
		},
		{
			caseName: "Gestation Cooldown doesn't decrement",
			hamster: &Hamster{
				Age:               0,
				Gestation:         true,
				GestationCooldown: 17,
				GestationPeriod:   1,
				Alive:             true,
			},
			expectedHamster: &Hamster{
				Age:               1,
				GestationCooldown: 17,
				Gestation:         true,
				GestationPeriod:   2,
				Alive:             true,
			},
		},
		{
			caseName: "Gestation Cooldown decrement",
			hamster: &Hamster{
				Age:               0,
				Gestation:         false,
				GestationCooldown: 17,
				GestationPeriod:   0,
				Alive:             true,
			},
			expectedHamster: &Hamster{
				Age:               1,
				GestationCooldown: 16,
				Gestation:         false,
				GestationPeriod:   0,
				Alive:             true,
			},
		},
		{
			caseName: "Hamster die",
			hamster: &Hamster{
				Age:               365 * 3,
				Gestation:         false,
				GestationCooldown: 17,
				GestationPeriod:   0,
				Alive:             true,
			},
			expectedHamster: &Hamster{
				Age:               365 * 3,
				Alive:             false,
				GestationCooldown: 17,
				Gestation:         false,
				GestationPeriod:   0,
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.caseName, func(t *testing.T) {
			if tc.hamster.Alive == !tc.expectedHamster.Alive {
				randomizer.FakeRandomizer(364)
			}
			tc.hamster.Grow()
			if !reflect.DeepEqual(tc.hamster, tc.expectedHamster) {
				t.Errorf("Got %v expect %v", tc.hamster, tc.expectedHamster)
			}
		})
	}
}

func TestHamster_Fuck(t *testing.T) {
	testCases := []struct {
		caseName       string
		hamster1       *Hamster
		hamster2       *Hamster
		expectedResult bool
		expectedError  error
	}{
		{
			caseName: "Same sexe",
			hamster1: &Hamster{
				Alive: true,
				Sexe:  MALE,
				Age:   GestationMinAge - 1,
			},
			hamster2: &Hamster{
				Alive: true,
				Sexe:  MALE,
				Age:   GestationMinAge + 1,
			},
			expectedResult: false,
			expectedError:  errors.New("can't fuck together"),
		},
		{
			caseName: "Hamster 1 too young",
			hamster1: &Hamster{
				Alive: true,
				Sexe:  MALE,
				Age:   GestationMinAge - 1,
			},
			hamster2: &Hamster{
				Alive: true,
				Sexe:  FEMALE,
				Age:   GestationMinAge + 1,
			},
			expectedResult: false,
			expectedError:  errors.New("one of the hamster is too young"),
		},
		{
			caseName: "Hamster 2 too young",
			hamster1: &Hamster{
				Alive: false,
				Sexe:  MALE,
				Age:   GestationMinAge + 1,
			},
			hamster2: &Hamster{
				Alive: true,
				Sexe:  FEMALE,
				Age:   GestationMinAge - 1,
			},
			expectedResult: false,
			expectedError:  errors.New("one of the hamster is too young"),
		},
		{
			caseName: "Can have sexe and success",
			hamster1: &Hamster{
				Alive: false,
				Sexe:  MALE,
				Age:   GestationMinAge,
			},
			hamster2: &Hamster{
				Alive: true,
				Sexe:  FEMALE,
				Age:   GestationMinAge,
			},
			expectedResult: true,
			expectedError:  nil,
		},
		{
			caseName: "Can have sexe and fail",
			hamster1: &Hamster{
				Alive: false,
				Sexe:  MALE,
				Age:   GestationMinAge,
			},
			hamster2: &Hamster{
				Alive: true,
				Sexe:  FEMALE,
				Age:   GestationMinAge,
			},
			expectedResult: false,
			expectedError:  nil,
		},
		{
			caseName: "Can have sexe and success. Inverted female and male",
			hamster2: &Hamster{
				Alive: false,
				Sexe:  MALE,
				Age:   GestationMinAge,
			},
			hamster1: &Hamster{
				Alive: true,
				Sexe:  FEMALE,
				Age:   GestationMinAge,
			},
			expectedResult: true,
			expectedError:  nil,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.caseName, func(t *testing.T) {
			if tc.expectedResult {
				randomizer.FakeRandomizer(60)
			} else {
				randomizer.FakeRandomizer(80)
			}
			bool, err := tc.hamster1.Fuck(tc.hamster2)
			if tc.expectedResult != bool {
				t.Errorf("Fuck result does not match expectation. \n Got : %t \n Get : %t", tc.expectedResult, bool)
			}
			if tc.expectedError != nil && err != nil && tc.expectedError.Error() != err.Error() {
				t.Errorf("Fuck result does not match expectation. \n Got : %s \n Get : %s", tc.expectedError, err)
			}

			if tc.expectedError == nil && err != nil {
				t.Errorf("Fuck result does not match expectation. \n Got error : %s ", err)
			}

			if tc.expectedResult {
				var female *Hamster
				if FEMALE == tc.hamster1.Sexe {
					female = tc.hamster1
				} else {
					female = tc.hamster2
				}
				if !female.Gestation {
					t.Errorf("Female is not in gestation")
				}
				if female.GestationPeriod != 0 {
					t.Errorf("Gestation period doesn't have been reseted")
				}
				if female.GestationCooldown != TotalGestationPeriod+GestationCooldown {
					t.Errorf("Gestation period doesn't have been reseted")
				}
			}

		})
	}
}

func Test_randSexe(t *testing.T) {
	tests := []struct {
		name    string
		want    string
		fakeGen int
	}{
		{
			name:    "Should be a male",
			fakeGen: 1,
			want:    MALE,
		},
		{
			name:    "Should be a female",
			fakeGen: 0,
			want:    FEMALE,
		},
	}
	for _, tt := range tests {
		randomizer.FakeRandomizer(tt.fakeGen)
		if got := randSexe(); got != tt.want {
			t.Errorf("%q. randSexe() = %v, want %v", tt.name, got, tt.want)
		}
	}
}

func Test_randNumber(t *testing.T) {
	type args struct {
		min int
		max int
	}
	tests := []struct {
		name    string
		args    args
		fakeGen int
		want    int
	}{
		{
			name: "Should add min to result",
			args: args{
				min: 1,
				max: 2,
			},
			fakeGen: 0,
			want:    1,
		},
	}
	for _, tt := range tests {
		randomizer.FakeRandomizer(tt.fakeGen)
		if got := randNumber(tt.args.min, tt.args.max); got != tt.want {
			t.Errorf("%q. randNumber() = %v, want %v", tt.name, got, tt.want)
		}
	}
}
