// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package identity_test

import (
	"fmt"
	"testing"

	qt "github.com/frankban/quicktest"
	"github.com/gohugoio/hugo/identity"
	"github.com/gohugoio/hugo/identity/identitytesting"
)

func BenchmarkIdentityManager(b *testing.B) {
	createIds := func(num int) []identity.Identity {
		ids := make([]identity.Identity, num)
		for i := range num {
			name := fmt.Sprintf("id%d", i)
			ids[i] = &testIdentity{base: name, name: name}
		}
		return ids
	}

	b.Run("identity.NewManager", func(b *testing.B) {
		for b.Loop() {
			m := identity.NewManager()
			if m == nil {
				b.Fatal("manager is nil")
			}
		}
	})

	b.Run("Add many", func(b *testing.B) {
		const size = 1000
		ids := createIds(size)
		im := identity.NewManager()

		b.ResetTimer()
		for i := 0; b.Loop(); i++ {
			im.AddIdentity(ids[i%size])
		}

		b.StopTimer()
	})

	b.Run("Nop StringIdentity const", func(b *testing.B) {
		const id = identity.StringIdentity("test")
		for b.Loop() {
			identity.NopManager.AddIdentity(id)
		}
	})

	b.Run("Nop StringIdentity const other package", func(b *testing.B) {
		for b.Loop() {
			identity.NopManager.AddIdentity(identitytesting.TestIdentity)
		}
	})

	b.Run("Nop StringIdentity var", func(b *testing.B) {
		id := identity.StringIdentity("test")
		for b.Loop() {
			identity.NopManager.AddIdentity(id)
		}
	})

	b.Run("Nop pointer identity", func(b *testing.B) {
		id := &testIdentity{base: "a", name: "b"}
		for b.Loop() {
			identity.NopManager.AddIdentity(id)
		}
	})

	b.Run("Nop Anonymous", func(b *testing.B) {
		for b.Loop() {
			identity.NopManager.AddIdentity(identity.Anonymous)
		}
	})
}

func BenchmarkIsNotDependent(b *testing.B) {
	runBench := func(b *testing.B, id1, id2 identity.Identity) {
		for b.Loop() {
			isNotDependent(id1, id2)
		}
	}

	newNestedManager := func(depth, count int) identity.Manager {
		m1 := identity.NewManager()
		for range depth {
			m2 := identity.NewManager()
			m1.AddIdentity(m2)
			for j := range count {
				id := fmt.Sprintf("id%d", j)
				m2.AddIdentity(&testIdentity{id, id, "", ""})
			}
			m1 = m2
		}
		return m1
	}

	type depthCount struct {
		depth int
		count int
	}

	for _, dc := range []depthCount{{10, 5}} {
		b.Run(fmt.Sprintf("Nested not found %d %d", dc.depth, dc.count), func(b *testing.B) {
			im := newNestedManager(dc.depth, dc.count)
			id1 := identity.StringIdentity("idnotfound")
			b.ResetTimer()
			runBench(b, im, id1)
		})
	}
}

func TestIdentityManager(t *testing.T) {
	c := qt.New(t)

	newNestedManager := func() identity.Manager {
		m1 := identity.NewManager()
		m2 := identity.NewManager()
		m3 := identity.NewManager()
		m1.AddIdentity(
			testIdentity{"base", "id1", "", "pe1"},
			testIdentity{"base2", "id2", "eq1", ""},
			m2,
			m3,
		)

		m2.AddIdentity(testIdentity{"base4", "id4", "", ""})

		return m1
	}

	c.Run("Anonymous", func(c *qt.C) {
		im := newNestedManager()
		c.Assert(im.GetIdentity(), qt.Equals, identity.Anonymous)
		im.AddIdentity(identity.Anonymous)
		c.Assert(isNotDependent(identity.Anonymous, identity.Anonymous), qt.IsTrue)
	})

	c.Run("GenghisKhan", func(c *qt.C) {
		c.Assert(isNotDependent(identity.GenghisKhan, identity.GenghisKhan), qt.IsTrue)
	})
}

type testIdentity struct {
	base string
	name string

	idEq         string
	idProbablyEq string
}

func (id testIdentity) Eq(other any) bool {
	ot, ok := other.(testIdentity)
	if !ok {
		return false
	}
	if ot.idEq == "" || id.idEq == "" {
		return false
	}
	return ot.idEq == id.idEq
}

func (id testIdentity) IdentifierBase() string {
	return id.base
}

func (id testIdentity) Name() string {
	return id.name
}

func (id testIdentity) ProbablyEq(other any) bool {
	ot, ok := other.(testIdentity)
	if !ok {
		return false
	}
	if ot.idProbablyEq == "" || id.idProbablyEq == "" {
		return false
	}
	return ot.idProbablyEq == id.idProbablyEq
}

func isNotDependent(a, b identity.Identity) bool {
	f := identity.NewFinder(identity.FinderConfig{})
	r := f.Contains(b, a, -1)
	return r == 0
}
