package handlers

import (
	"context"
	"errors"
	"sync"
	"time"

	"github.com/google/uuid"
	taskmodel "xtablo-backend/internal/tasks"
)

// InMemoryAuthRepository exists only as test support.
// It must not be used as a production auth/session store.
type InMemoryAuthRepository struct {
	mu          sync.RWMutex
	authUsers   map[string]AuthUser
	publicUsers map[uuid.UUID]PublicUser
	sessions    map[string]Session
	tablos      map[uuid.UUID]TabloRecord
	tasks       map[uuid.UUID]TaskRecord
}

// NewInMemoryAuthRepository creates a testing-only auth repository.
// Use the Postgres-backed repository in real application flows.
func NewInMemoryAuthRepository() *InMemoryAuthRepository {
	repo := &InMemoryAuthRepository{
		authUsers:   map[string]AuthUser{},
		publicUsers: map[uuid.UUID]PublicUser{},
		sessions:    map[string]Session{},
		tablos:      map[uuid.UUID]TabloRecord{},
		tasks:       map[uuid.UUID]TaskRecord{},
	}

	demoHash, err := hashPassword("xtablo-demo")
	if err != nil {
		panic(err)
	}
	if _, err := repo.CreateAuthUser(context.Background(), CreateAuthUserInput{
		Email:             "demo@xtablo.com",
		EncryptedPassword: demoHash,
		DisplayName:       "demo",
	}); err != nil {
		panic(err)
	}

	return repo
}

func (r *InMemoryAuthRepository) CreateAuthUser(_ context.Context, input CreateAuthUserInput) (uuid.UUID, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if _, exists := r.authUsers[input.Email]; exists {
		return uuid.Nil, ErrUserAlreadyExists
	}

	id := uuid.New()
	now := time.Now().UTC()
	authUser := AuthUser{
		ID:                id,
		Email:             input.Email,
		EncryptedPassword: input.EncryptedPassword,
		CreatedAt:         now,
		UpdatedAt:         now,
	}
	publicUser := PublicUser{
		ID:          id,
		Email:       input.Email,
		DisplayName: input.DisplayName,
		CreatedAt:   now,
		UpdatedAt:   now,
	}

	r.authUsers[input.Email] = authUser
	r.publicUsers[id] = publicUser
	logStoreMutation("create_user", input.Email, "", len(r.authUsers), 0)
	return id, nil
}

func (r *InMemoryAuthRepository) GetAuthUserByEmail(_ context.Context, email string) (AuthUser, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	user, ok := r.authUsers[email]
	if !ok {
		return AuthUser{}, ErrUserNotFound
	}
	return user, nil
}

func (r *InMemoryAuthRepository) GetPublicUserByID(_ context.Context, id uuid.UUID) (PublicUser, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	user, ok := r.publicUsers[id]
	if !ok {
		return PublicUser{}, ErrUserNotFound
	}
	return user, nil
}

func (r *InMemoryAuthRepository) CreateSession(_ context.Context, token string, userID uuid.UUID, expiresAt time.Time) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	now := time.Now().UTC()
	r.sessions[token] = Session{
		Token:     token,
		UserID:    userID,
		CreatedAt: now,
		UpdatedAt: now,
		ExpiresAt: expiresAt,
	}
	return nil
}

func (r *InMemoryAuthRepository) GetSessionByToken(_ context.Context, token string) (Session, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	session, ok := r.sessions[token]
	if !ok {
		return Session{}, ErrSessionNotFound
	}
	return session, nil
}

func (r *InMemoryAuthRepository) DeleteSessionByToken(_ context.Context, token string) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	if _, ok := r.sessions[token]; !ok {
		return ErrSessionNotFound
	}
	delete(r.sessions, token)
	return nil
}

func (r *InMemoryAuthRepository) CreateTask(_ context.Context, input CreateTaskInput) (TaskRecord, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if err := r.validateTaskInputLocked(input.OwnerID, input.TabloID, input.Status, input.AssigneeID, input.IsEtape, input.ParentTaskID); err != nil {
		return TaskRecord{}, err
	}

	now := time.Now().UTC()
	record := TaskRecord{
		ID:           uuid.New(),
		OwnerID:      input.OwnerID,
		TabloID:      input.TabloID,
		Title:        input.Title,
		Description:  input.Description,
		Status:       input.Status,
		AssigneeID:   input.AssigneeID,
		IsEtape:      input.IsEtape,
		ParentTaskID: input.ParentTaskID,
		DueDate:      input.DueDate,
		CreatedAt:    now,
		UpdatedAt:    now,
	}

	r.tasks[record.ID] = record
	return record, nil
}

func (r *InMemoryAuthRepository) ListTasksByTablo(_ context.Context, input ListTasksByTabloInput) ([]TaskRecord, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	tasks := make([]TaskRecord, 0)
	for _, record := range r.tasks {
		if record.OwnerID != input.OwnerID || record.TabloID != input.TabloID || record.DeletedAt != nil {
			continue
		}
		tasks = append(tasks, record)
	}

	sortTasksByCreatedAt(tasks)
	return tasks, nil
}

func (r *InMemoryAuthRepository) ListTasksByOwner(_ context.Context, ownerID uuid.UUID) ([]TaskRecord, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	tasks := make([]TaskRecord, 0)
	for _, record := range r.tasks {
		if record.OwnerID != ownerID || record.DeletedAt != nil {
			continue
		}
		tasks = append(tasks, record)
	}

	sortTasksByCreatedAt(tasks)
	return tasks, nil
}

func (r *InMemoryAuthRepository) GetTaskByID(_ context.Context, taskID uuid.UUID, ownerID uuid.UUID) (TaskRecord, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	record, ok := r.tasks[taskID]
	if !ok || record.OwnerID != ownerID || record.DeletedAt != nil {
		return TaskRecord{}, taskmodel.ErrNotFound
	}
	return record, nil
}

func (r *InMemoryAuthRepository) UpdateTask(_ context.Context, input UpdateTaskInput) (TaskRecord, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	record, ok := r.tasks[input.ID]
	if !ok || record.OwnerID != input.OwnerID || record.DeletedAt != nil {
		return TaskRecord{}, taskmodel.ErrNotFound
	}
	if input.TabloID == uuid.Nil {
		input.TabloID = record.TabloID
	}
	if err := r.validateTaskInputLocked(input.OwnerID, input.TabloID, input.Status, input.AssigneeID, record.IsEtape, input.ParentTaskID); err != nil {
		return TaskRecord{}, err
	}

	record.TabloID = input.TabloID
	record.Title = input.Title
	record.Description = input.Description
	record.Status = input.Status
	record.AssigneeID = input.AssigneeID
	record.ParentTaskID = input.ParentTaskID
	record.DueDate = input.DueDate
	record.UpdatedAt = time.Now().UTC()

	r.tasks[input.ID] = record
	return record, nil
}

func (r *InMemoryAuthRepository) SoftDeleteTask(_ context.Context, taskID uuid.UUID, ownerID uuid.UUID) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	record, ok := r.tasks[taskID]
	if !ok || record.OwnerID != ownerID || record.DeletedAt != nil {
		return taskmodel.ErrNotFound
	}

	now := time.Now().UTC()
	record.DeletedAt = &now
	record.UpdatedAt = now
	r.tasks[taskID] = record

	if record.IsEtape {
		for id, child := range r.tasks {
			if child.DeletedAt != nil || child.ParentTaskID == nil {
				continue
			}
			if *child.ParentTaskID != taskID {
				continue
			}
			child.ParentTaskID = nil
			child.UpdatedAt = now
			r.tasks[id] = child
		}
	}

	return nil
}

func (r *InMemoryAuthRepository) validateTaskInputLocked(ownerID uuid.UUID, tabloID uuid.UUID, status TaskStatus, assigneeID *uuid.UUID, isEtape bool, parentTaskID *uuid.UUID) error {
	tablo, ok := r.tablos[tabloID]
	if !ok || tablo.OwnerID != ownerID || tablo.DeletedAt != nil {
		return errors.New("tablo not found")
	}

	if _, err := taskmodel.ParseStatus(string(status)); err != nil {
		return err
	}

	if assigneeID != nil {
		if _, ok := r.publicUsers[*assigneeID]; !ok {
			return taskmodel.ErrInvalidAssignee
		}
	}

	if isEtape && parentTaskID != nil {
		return taskmodel.ErrInvalidParent
	}

	if parentTaskID == nil {
		return nil
	}

	parent, ok := r.tasks[*parentTaskID]
	if !ok || parent.DeletedAt != nil {
		return taskmodel.ErrInvalidParent
	}
	if parent.OwnerID != ownerID || parent.TabloID != tabloID || !parent.IsEtape {
		return taskmodel.ErrInvalidParent
	}
	return nil
}

func sortTasksByCreatedAt(tasks []TaskRecord) {
	for i := 0; i < len(tasks); i++ {
		for j := i + 1; j < len(tasks); j++ {
			if tasks[j].CreatedAt.Before(tasks[i].CreatedAt) {
				tasks[i], tasks[j] = tasks[j], tasks[i]
			}
		}
	}
}
