package testutil

import (
	"context"
	"strings"

	"github.com/elfoundation/hatch/internal/store"
)

// FakeRepository implements store.Repository for tests.
type FakeRepository struct {
	Endpoints map[string]*store.Endpoint
	Requests  []*store.Request
	Mocks     map[string]*store.MockConfig

	// Space-related fields
	Spaces          map[string]*store.Space
	Photos          map[string]*store.Photo
	FaceEmbeddings  map[string]*store.FaceEmbedding
	RateLimits      map[string]*store.RateLimit
}

// NewFakeRepository creates a new FakeRepository.
func NewFakeRepository() *FakeRepository {
	return &FakeRepository{
		Endpoints:       map[string]*store.Endpoint{},
		Mocks:          map[string]*store.MockConfig{},
		Spaces:          map[string]*store.Space{},
		Photos:          map[string]*store.Photo{},
		FaceEmbeddings:  map[string]*store.FaceEmbedding{},
		RateLimits:      map[string]*store.RateLimit{},
	}
}

func (f *FakeRepository) CreateEndpoint(_ context.Context, url string) (*store.Endpoint, error) {
	e := &store.Endpoint{ID: url, URL: url, CreatedAt: "t", UpdatedAt: "t"}
	f.Endpoints[url] = e
	return e, nil
}

func (f *FakeRepository) GetEndpoint(_ context.Context, id string) (*store.Endpoint, error) {
	e, ok := f.Endpoints[id]
	if !ok {
		return nil, errNotFound
	}
	return e, nil
}

func (f *FakeRepository) AppendRequest(_ context.Context, eid string, r *store.Request) error {
	r.ID = "req-" + string(rune(len(f.Requests)+'0'))
	r.EndpointID = eid
	f.Requests = append(f.Requests, r)
	return nil
}

func (f *FakeRepository) GetRequest(_ context.Context, id string) (*store.Request, error) {
	for _, r := range f.Requests {
		if r.ID == id {
			return r, nil
		}
	}
	return nil, errNotFound
}

func (f *FakeRepository) ListRequests(_ context.Context, _ string, _ int) ([]*store.Request, error) {
	return f.Requests, nil
}

func (f *FakeRepository) SearchRequests(_ context.Context, _ string, query string, limit int) ([]*store.Request, error) {
	if query == "" {
		return f.Requests, nil
	}
	var matched []*store.Request
	for _, r := range f.Requests {
		if strings.Contains(r.Method, query) ||
			strings.Contains(r.Path, query) ||
			strings.Contains(r.Headers, query) ||
			strings.Contains(r.Query, query) ||
			strings.Contains(string(r.Body), query) {
			matched = append(matched, r)
			if limit > 0 && len(matched) >= limit {
				break
			}
		}
	}
	return matched, nil
}

func (f *FakeRepository) GetMock(_ context.Context, endpointID string) (*store.MockConfig, error) {
	m, ok := f.Mocks[endpointID]
	if !ok {
		return nil, errNotFound
	}
	return m, nil
}

func (f *FakeRepository) SetMock(_ context.Context, mock *store.MockConfig) error {
	f.Mocks[mock.EndpointID] = mock
	return nil
}

func (f *FakeRepository) Close() error { return nil }

func (f *FakeRepository) Ping(_ context.Context) error { return nil }

// Space operations

func (f *FakeRepository) CreateSpace(_ context.Context, space *store.Space) (*store.Space, error) {
	f.Spaces[space.Slug] = space
	return space, nil
}

func (f *FakeRepository) GetSpaceBySlug(_ context.Context, slug string) (*store.Space, error) {
	space, ok := f.Spaces[slug]
	if !ok {
		return nil, errNotFound
	}
	return space, nil
}

func (f *FakeRepository) UpdateSpace(_ context.Context, space *store.Space) (*store.Space, error) {
	f.Spaces[space.Slug] = space
	return space, nil
}

func (f *FakeRepository) DeleteSpace(_ context.Context, id string) error {
	for slug, space := range f.Spaces {
		if space.ID == id {
			delete(f.Spaces, slug)
			return nil
		}
	}
	return errNotFound
}

func (f *FakeRepository) ListPublicSpaces(_ context.Context) ([]*store.Space, error) {
	var spaces []*store.Space
	for _, space := range f.Spaces {
		if space.IsPublic {
			spaces = append(spaces, space)
		}
	}
	return spaces, nil
}

// Photo operations

func (f *FakeRepository) CreatePhoto(_ context.Context, photo *store.Photo) (*store.Photo, error) {
	f.Photos[photo.ID] = photo
	return photo, nil
}

func (f *FakeRepository) GetPhoto(_ context.Context, id string) (*store.Photo, error) {
	photo, ok := f.Photos[id]
	if !ok {
		return nil, errNotFound
	}
	return photo, nil
}

func (f *FakeRepository) ListPhotosBySpace(_ context.Context, spaceID string, limit int) ([]*store.Photo, error) {
	var photos []*store.Photo
	for _, photo := range f.Photos {
		if photo.SpaceID == spaceID {
			photos = append(photos, photo)
			if limit > 0 && len(photos) >= limit {
				break
			}
		}
	}
	return photos, nil
}

func (f *FakeRepository) DeletePhoto(_ context.Context, id string) error {
	for photoID := range f.Photos {
		if photoID == id {
			delete(f.Photos, photoID)
			return nil
		}
	}
	return errNotFound
}

// Face embedding operations

func (f *FakeRepository) CreateFaceEmbedding(_ context.Context, embedding *store.FaceEmbedding) (*store.FaceEmbedding, error) {
	f.FaceEmbeddings[embedding.ID] = embedding
	return embedding, nil
}

func (f *FakeRepository) ListFaceEmbeddingsBySpace(_ context.Context, spaceID string) ([]*store.FaceEmbedding, error) {
	var embeddings []*store.FaceEmbedding
	for _, embedding := range f.FaceEmbeddings {
		if embedding.SpaceID == spaceID {
			embeddings = append(embeddings, embedding)
		}
	}
	return embeddings, nil
}

func (f *FakeRepository) SearchFaceEmbeddings(_ context.Context, spaceID string, embedding []float32, limit int) ([]*store.FaceEmbedding, error) {
	// For fake repo, just return all embeddings in the space
	return f.ListFaceEmbeddingsBySpace(nil, spaceID)
}

// Rate limiting operations

func (f *FakeRepository) CheckRateLimit(_ context.Context, ipAddress, spaceID string, limit, windowMinutes int) (bool, error) {
	// For fake repo, always allow
	return true, nil
}

func (f *FakeRepository) IncrementRateLimit(_ context.Context, ipAddress, spaceID string, windowMinutes int) error {
	// For fake repo, do nothing
	return nil
}

type notFoundError struct{}

func (e *notFoundError) Error() string { return "not found" }

var errNotFound = &notFoundError{}
