package processor

import (
	"context"
	"fmt"

	"eslogad-be/internal/entities"
	"eslogad-be/internal/repository"

	"github.com/google/uuid"
)

// DepartmentProcessor handles all department-related business logic
type DepartmentProcessor interface {
	Create(ctx context.Context, department *entities.Department) error
	Get(ctx context.Context, id uuid.UUID) (*entities.Department, error)
	GetByPath(ctx context.Context, path string) (*entities.Department, error)
	Update(ctx context.Context, department *entities.Department) error
	Delete(ctx context.Context, id uuid.UUID) error
	List(ctx context.Context, search string, limit, offset int) ([]entities.Department, int64, error)
	
	// Hierarchy operations
	GetChildren(ctx context.Context, parentID uuid.UUID) ([]entities.Department, error)
	GetAllDescendants(ctx context.Context, parentID uuid.UUID) ([]entities.Department, error)
	UpdateChildrenPaths(ctx context.Context, oldPath, newPath string) error
	ValidateHierarchy(ctx context.Context, departmentID, newParentID uuid.UUID) error
	
	// Tree building operations
	GetDepartmentWithDescendants(ctx context.Context, departmentID uuid.UUID) ([]entities.Department, error)
	GetDepartmentWithParentAndSiblings(ctx context.Context, departmentID uuid.UUID) ([]entities.Department, error)
	GetAllDepartments(ctx context.Context) ([]entities.Department, error)
	GetDepartmentsByRootPath(ctx context.Context, rootPath string) ([]entities.Department, error)
}

type DepartmentProcessorImpl struct {
	departmentRepo *repository.DepartmentRepository
	txManager      *repository.TxManager
}

// NewDepartmentProcessor creates a new department processor
func NewDepartmentProcessor(
	departmentRepo *repository.DepartmentRepository,
	txManager *repository.TxManager,
) *DepartmentProcessorImpl {
	return &DepartmentProcessorImpl{
		departmentRepo: departmentRepo,
		txManager:      txManager,
	}
}

// Create creates a new department
func (p *DepartmentProcessorImpl) Create(ctx context.Context, department *entities.Department) error {
	// Build path based on parent
	if department.ParentDepartmentID != nil && *department.ParentDepartmentID != uuid.Nil {
		parent, err := p.departmentRepo.GetByID(ctx, *department.ParentDepartmentID)
		if err != nil {
			return fmt.Errorf("parent department not found: %w", err)
		}
		department.Path = parent.Path + "." + department.Code
	} else {
		department.Path = department.Code
	}
	
	return p.departmentRepo.Create(ctx, department)
}

// Get retrieves a department by ID
func (p *DepartmentProcessorImpl) Get(ctx context.Context, id uuid.UUID) (*entities.Department, error) {
	return p.departmentRepo.Get(ctx, id)
}

// GetByPath retrieves a department by its path
func (p *DepartmentProcessorImpl) GetByPath(ctx context.Context, path string) (*entities.Department, error) {
	return p.departmentRepo.GetByPath(ctx, path)
}

// Update updates a department with hierarchy validation
func (p *DepartmentProcessorImpl) Update(ctx context.Context, department *entities.Department) error {
	return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
		// Get the current department state
		current, err := p.departmentRepo.Get(txCtx, department.ID)
		if err != nil {
			return err
		}
		
		// Store old values for comparison
		oldPath := current.Path
		oldParentID := current.ParentDepartmentID
		pathChanged := false
		
		// Update path if parent or code changed
		if department.ParentDepartmentID != oldParentID || department.Code != current.Code {
			// Rebuild path
			if department.ParentDepartmentID == nil {
				department.Path = department.Code
			} else {
				parent, err := p.departmentRepo.GetByID(txCtx, *department.ParentDepartmentID)
				if err != nil {
					return fmt.Errorf("parent department not found: %w", err)
				}
				department.Path = parent.Path + "." + department.Code
			}
			
			if department.Path != oldPath {
				pathChanged = true
			}
		}
		
		// Update the department
		if err := p.departmentRepo.Update(txCtx, department); err != nil {
			return err
		}
		
		// Update children paths if necessary
		if pathChanged {
			if err := p.updateChildrenPathsRecursively(txCtx, department.ID, department.Path); err != nil {
				return fmt.Errorf("failed to update children paths: %w", err)
			}
		}
		
		return nil
	})
}

// Delete deletes a department
func (p *DepartmentProcessorImpl) Delete(ctx context.Context, id uuid.UUID) error {
	return p.departmentRepo.Delete(ctx, id)
}

// List lists departments with pagination
func (p *DepartmentProcessorImpl) List(ctx context.Context, search string, limit, offset int) ([]entities.Department, int64, error) {
	return p.departmentRepo.List(ctx, search, limit, offset)
}

// GetChildren gets direct children of a department
func (p *DepartmentProcessorImpl) GetChildren(ctx context.Context, parentID uuid.UUID) ([]entities.Department, error) {
	// First get the parent department to get its path
	parent, err := p.departmentRepo.Get(ctx, parentID)
	if err != nil {
		return nil, err
	}
	if parent == nil {
		return []entities.Department{}, nil
	}
	return p.departmentRepo.GetChildren(ctx, parent.Path)
}

// GetAllDescendants gets all descendants of a department
func (p *DepartmentProcessorImpl) GetAllDescendants(ctx context.Context, parentID uuid.UUID) ([]entities.Department, error) {
	return p.departmentRepo.GetAllDescendants(ctx, parentID)
}

// UpdateChildrenPaths updates paths for all children
func (p *DepartmentProcessorImpl) UpdateChildrenPaths(ctx context.Context, oldPath, newPath string) error {
	return p.departmentRepo.UpdateChildrenPaths(ctx, oldPath, newPath)
}

// ValidateHierarchy validates that setting newParentID won't create circular references
func (p *DepartmentProcessorImpl) ValidateHierarchy(ctx context.Context, departmentID, newParentID uuid.UUID) error {
	// Can't be its own parent
	if departmentID == newParentID {
		return fmt.Errorf("department cannot be its own parent")
	}
	
	// Check if new parent is a descendant
	descendants, err := p.GetAllDescendants(ctx, departmentID)
	if err != nil {
		return fmt.Errorf("failed to check descendants: %w", err)
	}
	
	for _, desc := range descendants {
		if desc.ID == newParentID {
			return fmt.Errorf("cannot set a descendant as parent (circular reference)")
		}
	}
	
	return nil
}

// GetDepartmentWithDescendants gets a department and all its descendants
func (p *DepartmentProcessorImpl) GetDepartmentWithDescendants(ctx context.Context, departmentID uuid.UUID) ([]entities.Department, error) {
	// Get the department
	dept, err := p.departmentRepo.Get(ctx, departmentID)
	if err != nil {
		return nil, err
	}
	
	departments := []entities.Department{*dept}
	
	// Get all descendants
	descendants, err := p.departmentRepo.GetAllDescendants(ctx, departmentID)
	if err == nil {
		departments = append(departments, descendants...)
	}
	
	return departments, nil
}

// GetDepartmentWithParentAndSiblings gets a department with its parent and all siblings
func (p *DepartmentProcessorImpl) GetDepartmentWithParentAndSiblings(ctx context.Context, departmentID uuid.UUID) ([]entities.Department, error) {
	// Get the department
	dept, err := p.departmentRepo.Get(ctx, departmentID)
	if err != nil {
		return nil, err
	}
	
	var departments []entities.Department
	
	// If has parent, get parent and all its descendants
	if dept.ParentDepartmentID != nil {
		parent, err := p.departmentRepo.Get(ctx, *dept.ParentDepartmentID)
		if err == nil {
			departments = append(departments, *parent)
			
			// Get all descendants of parent (includes siblings)
			descendants, err := p.departmentRepo.GetAllDescendants(ctx, parent.ID)
			if err == nil {
				departments = append(departments, descendants...)
			}
		} else {
			// Fallback to just department and its descendants
			return p.GetDepartmentWithDescendants(ctx, departmentID)
		}
	} else {
		// No parent, just get department and descendants
		return p.GetDepartmentWithDescendants(ctx, departmentID)
	}
	
	return departments, nil
}

// GetAllDepartments gets all departments in the system
func (p *DepartmentProcessorImpl) GetAllDepartments(ctx context.Context) ([]entities.Department, error) {
	departments, _, err := p.departmentRepo.List(ctx, "", 0, 0)
	return departments, err
}

// GetDepartmentsByRootPath gets departments starting from a specific path
func (p *DepartmentProcessorImpl) GetDepartmentsByRootPath(ctx context.Context, rootPath string) ([]entities.Department, error) {
	// Find the root department
	rootDept, err := p.departmentRepo.GetByPath(ctx, rootPath)
	if err != nil {
		return nil, err
	}
	
	return p.GetDepartmentWithDescendants(ctx, rootDept.ID)
}

// updateChildrenPathsRecursively updates paths for all children recursively
func (p *DepartmentProcessorImpl) updateChildrenPathsRecursively(ctx context.Context, parentID uuid.UUID, parentPath string) error {
	children, err := p.departmentRepo.GetChildren(ctx, parentPath)
	if err != nil {
		return err
	}
	
	for _, child := range children {
		// Update child's path
		child.Path = parentPath + "." + child.Code
		if err := p.departmentRepo.Update(ctx, &child); err != nil {
			return err
		}
		
		// Recursively update grandchildren
		if err := p.updateChildrenPathsRecursively(ctx, child.ID, child.Path); err != nil {
			return err
		}
	}
	
	return nil
}