package service

import (
	"context"
	"sort"
	"strings"

	"eslogad-be/config"
	"eslogad-be/internal/contract"
	"eslogad-be/internal/entities"
	"eslogad-be/internal/repository"
	"eslogad-be/internal/transformer"

	"github.com/google/uuid"
)

type MasterServiceImpl struct {
	labelRepo       *repository.LabelRepository
	priorityRepo    *repository.PriorityRepository
	institutionRepo *repository.InstitutionRepository
	dispRepo        *repository.DispositionActionRepository
	departmentRepo  *repository.DepartmentRepository
	config          *config.Config
}

func NewMasterService(label *repository.LabelRepository, priority *repository.PriorityRepository, institution *repository.InstitutionRepository, disp *repository.DispositionActionRepository, department *repository.DepartmentRepository, cfg *config.Config) *MasterServiceImpl {
	return &MasterServiceImpl{labelRepo: label, priorityRepo: priority, institutionRepo: institution, dispRepo: disp, departmentRepo: department, config: cfg}
}

// Labels
func (s *MasterServiceImpl) CreateLabel(ctx context.Context, req *contract.CreateLabelRequest) (*contract.LabelResponse, error) {
	entity := &entities.Label{Name: req.Name, Color: req.Color}
	if err := s.labelRepo.Create(ctx, entity); err != nil {
		return nil, err
	}
	resp := transformer.LabelsToContract([]entities.Label{*entity})[0]
	return &resp, nil
}
func (s *MasterServiceImpl) UpdateLabel(ctx context.Context, id uuid.UUID, req *contract.UpdateLabelRequest) (*contract.LabelResponse, error) {
	entity := &entities.Label{ID: id}
	if req.Name != nil {
		entity.Name = *req.Name
	}
	if req.Color != nil {
		entity.Color = req.Color
	}
	if err := s.labelRepo.Update(ctx, entity); err != nil {
		return nil, err
	}
	e, err := s.labelRepo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	resp := transformer.LabelsToContract([]entities.Label{*e})[0]
	return &resp, nil
}
func (s *MasterServiceImpl) DeleteLabel(ctx context.Context, id uuid.UUID) error {
	return s.labelRepo.Delete(ctx, id)
}
func (s *MasterServiceImpl) ListLabels(ctx context.Context) (*contract.ListLabelsResponse, error) {
	list, err := s.labelRepo.List(ctx)
	if err != nil {
		return nil, err
	}
	return &contract.ListLabelsResponse{Labels: transformer.LabelsToContract(list)}, nil
}

// Priorities
func (s *MasterServiceImpl) CreatePriority(ctx context.Context, req *contract.CreatePriorityRequest) (*contract.PriorityResponse, error) {
	entity := &entities.Priority{Name: req.Name, Level: req.Level}
	if err := s.priorityRepo.Create(ctx, entity); err != nil {
		return nil, err
	}
	resp := transformer.PrioritiesToContract([]entities.Priority{*entity})[0]
	return &resp, nil
}
func (s *MasterServiceImpl) UpdatePriority(ctx context.Context, id uuid.UUID, req *contract.UpdatePriorityRequest) (*contract.PriorityResponse, error) {
	entity := &entities.Priority{ID: id}
	if req.Name != nil {
		entity.Name = *req.Name
	}
	if req.Level != nil {
		entity.Level = *req.Level
	}
	if err := s.priorityRepo.Update(ctx, entity); err != nil {
		return nil, err
	}
	e, err := s.priorityRepo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	resp := transformer.PrioritiesToContract([]entities.Priority{*e})[0]
	return &resp, nil
}
func (s *MasterServiceImpl) DeletePriority(ctx context.Context, id uuid.UUID) error {
	return s.priorityRepo.Delete(ctx, id)
}
func (s *MasterServiceImpl) ListPriorities(ctx context.Context) (*contract.ListPrioritiesResponse, error) {
	list, err := s.priorityRepo.List(ctx)
	if err != nil {
		return nil, err
	}
	return &contract.ListPrioritiesResponse{Priorities: transformer.PrioritiesToContract(list)}, nil
}

// Institutions
func (s *MasterServiceImpl) CreateInstitution(ctx context.Context, req *contract.CreateInstitutionRequest) (*contract.InstitutionResponse, error) {
	entity := &entities.Institution{Name: req.Name, Type: entities.InstitutionType(req.Type), Address: req.Address, ContactPerson: req.ContactPerson, Phone: req.Phone, Email: req.Email}
	if err := s.institutionRepo.Create(ctx, entity); err != nil {
		return nil, err
	}
	resp := transformer.InstitutionsToContract([]entities.Institution{*entity})[0]
	return &resp, nil
}
func (s *MasterServiceImpl) UpdateInstitution(ctx context.Context, id uuid.UUID, req *contract.UpdateInstitutionRequest) (*contract.InstitutionResponse, error) {
	entity := &entities.Institution{ID: id}
	if req.Name != nil {
		entity.Name = *req.Name
	}
	if req.Type != nil {
		entity.Type = entities.InstitutionType(*req.Type)
	}
	if req.Address != nil {
		entity.Address = req.Address
	}
	if req.ContactPerson != nil {
		entity.ContactPerson = req.ContactPerson
	}
	if req.Phone != nil {
		entity.Phone = req.Phone
	}
	if req.Email != nil {
		entity.Email = req.Email
	}
	if err := s.institutionRepo.Update(ctx, entity); err != nil {
		return nil, err
	}
	e, err := s.institutionRepo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	resp := transformer.InstitutionsToContract([]entities.Institution{*e})[0]
	return &resp, nil
}
func (s *MasterServiceImpl) DeleteInstitution(ctx context.Context, id uuid.UUID) error {
	return s.institutionRepo.Delete(ctx, id)
}
func (s *MasterServiceImpl) ListInstitutions(ctx context.Context, req *contract.ListInstitutionsRequest) (*contract.ListInstitutionsResponse, error) {
	list, err := s.institutionRepo.ListWithSearch(ctx, req.Search)
	if err != nil {
		return nil, err
	}
	return &contract.ListInstitutionsResponse{Institutions: transformer.InstitutionsToContract(list)}, nil
}

// Disposition Actions
func (s *MasterServiceImpl) CreateDispositionAction(ctx context.Context, req *contract.CreateDispositionActionRequest) (*contract.DispositionActionResponse, error) {
	entity := &entities.DispositionAction{Code: req.Code, Label: req.Label, Description: req.Description}
	if req.RequiresNote != nil {
		entity.RequiresNote = *req.RequiresNote
	}
	if req.GroupName != nil {
		entity.GroupName = req.GroupName
	}
	if req.SortOrder != nil {
		entity.SortOrder = req.SortOrder
	}
	if req.IsActive != nil {
		entity.IsActive = *req.IsActive
	}
	if err := s.dispRepo.Create(ctx, entity); err != nil {
		return nil, err
	}
	resp := transformer.DispositionActionsToContract([]entities.DispositionAction{*entity})[0]
	return &resp, nil
}
func (s *MasterServiceImpl) UpdateDispositionAction(ctx context.Context, id uuid.UUID, req *contract.UpdateDispositionActionRequest) (*contract.DispositionActionResponse, error) {
	entity := &entities.DispositionAction{ID: id}
	if req.Code != nil {
		entity.Code = *req.Code
	}
	if req.Label != nil {
		entity.Label = *req.Label
	}
	if req.Description != nil {
		entity.Description = req.Description
	}
	if req.RequiresNote != nil {
		entity.RequiresNote = *req.RequiresNote
	}
	if req.GroupName != nil {
		entity.GroupName = req.GroupName
	}
	if req.SortOrder != nil {
		entity.SortOrder = req.SortOrder
	}
	if req.IsActive != nil {
		entity.IsActive = *req.IsActive
	}
	if err := s.dispRepo.Update(ctx, entity); err != nil {
		return nil, err
	}
	e, err := s.dispRepo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	resp := transformer.DispositionActionsToContract([]entities.DispositionAction{*e})[0]
	return &resp, nil
}
func (s *MasterServiceImpl) DeleteDispositionAction(ctx context.Context, id uuid.UUID) error {
	return s.dispRepo.Delete(ctx, id)
}
func (s *MasterServiceImpl) ListDispositionActions(ctx context.Context) (*contract.ListDispositionActionsResponse, error) {
	list, err := s.dispRepo.List(ctx)
	if err != nil {
		return nil, err
	}
	return &contract.ListDispositionActionsResponse{Actions: transformer.DispositionActionsToContract(list)}, nil
}

// Departments
func (s *MasterServiceImpl) CreateDepartment(ctx context.Context, req *contract.CreateDepartmentRequest) (*contract.GetDepartmentResponse, error) {
	// Build the path based on parent
	var path string
	if req.ParentID != nil {
		// Get parent department to build the path
		parent, err := s.departmentRepo.GetByID(ctx, *req.ParentID)
		if err != nil {
			return nil, err
		}
		// Build path as parent.path + code
		path = parent.Path + "." + req.Code
	} else {
		// Root level department, just use the code as path
		path = req.Code
	}
	
	entity := &entities.Department{
		Name: req.Name,
		Code: req.Code,
		Path: path,
	}
	if err := s.departmentRepo.Create(ctx, entity); err != nil {
		return nil, err
	}
	// Get parent name if parent exists
	var parentName *string
	if req.ParentID != nil {
		if parent, err := s.departmentRepo.GetByID(ctx, *req.ParentID); err == nil {
			parentName = &parent.Name
		}
	}
	
	return &contract.GetDepartmentResponse{
		ID:         entity.ID,
		Name:       entity.Name,
		Code:       entity.Code,
		Path:       entity.Path,
		ParentID:   req.ParentID,
		ParentName: parentName,
		CreatedAt:  entity.CreatedAt,
		UpdatedAt:  entity.UpdatedAt,
	}, nil
}

func (s *MasterServiceImpl) GetDepartment(ctx context.Context, id uuid.UUID) (*contract.GetDepartmentResponse, error) {
	entity, err := s.departmentRepo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	
	// Derive parent_id and parent_name from path
	var parentID *uuid.UUID
	var parentName *string
	parts := strings.Split(entity.Path, ".")
	if len(parts) > 1 {
		// Has parent, try to find it
		parentPath := strings.Join(parts[:len(parts)-1], ".")
		if parent, err := s.departmentRepo.GetByPath(ctx, parentPath); err == nil {
			parentID = &parent.ID
			parentName = &parent.Name
		}
	}
	
	return &contract.GetDepartmentResponse{
		ID:         entity.ID,
		Name:       entity.Name,
		Code:       entity.Code,
		Path:       entity.Path,
		ParentID:   parentID,
		ParentName: parentName,
		CreatedAt:  entity.CreatedAt,
		UpdatedAt:  entity.UpdatedAt,
	}, nil
}

func (s *MasterServiceImpl) UpdateDepartment(ctx context.Context, id uuid.UUID, req *contract.UpdateDepartmentRequest) (*contract.GetDepartmentResponse, error) {
	entity, err := s.departmentRepo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	
	// Store the old path before changes
	oldPath := entity.Path
	
	if req.Name != nil {
		entity.Name = *req.Name
	}
	if req.Code != nil {
		entity.Code = *req.Code
	}
	
	// Rebuild path if parent is being changed or code is being changed
	if req.ParentID != nil || req.Code != nil {
		// Determine the code to use (new code if provided, otherwise existing)
		code := entity.Code
		if req.Code != nil {
			code = *req.Code
		}
		
		// Build the new path based on parent
		var path string
		if req.ParentID != nil {
			if *req.ParentID == uuid.Nil {
				// Moving to root level
				path = code
			} else {
				// Get parent department to build the path
				parent, err := s.departmentRepo.GetByID(ctx, *req.ParentID)
				if err != nil {
					return nil, err
				}
				// Build path as parent.path + code
				path = parent.Path + "." + code
			}
		} else if req.Code != nil {
			// Code changed but parent not specified, rebuild path with current parent
			// Extract parent path from current path
			parts := strings.Split(entity.Path, ".")
			if len(parts) > 1 {
				// Has parent, rebuild with new code
				parentPath := strings.Join(parts[:len(parts)-1], ".")
				path = parentPath + "." + code
			} else {
				// Root level, just use new code
				path = code
			}
		}
		
		if path != "" {
			entity.Path = path
		}
	}
	
	// Update the department
	if err := s.departmentRepo.Update(ctx, entity); err != nil {
		return nil, err
	}
	
	// If the path changed, update all children paths
	if oldPath != entity.Path {
		if err := s.departmentRepo.UpdateChildrenPaths(ctx, oldPath, entity.Path); err != nil {
			// Log the error but don't fail the operation
			// You might want to handle this differently based on your requirements
			// For now, we'll continue since the parent update succeeded
		}
	}
	
	// Derive parent_id and parent_name from path for response
	var parentID *uuid.UUID
	var parentName *string
	if req.ParentID != nil {
		parentID = req.ParentID
		// Get parent name
		if parent, err := s.departmentRepo.GetByID(ctx, *req.ParentID); err == nil {
			parentName = &parent.Name
		}
	} else {
		// Derive from path if not provided in request
		parts := strings.Split(entity.Path, ".")
		if len(parts) > 1 {
			parentPath := strings.Join(parts[:len(parts)-1], ".")
			if parent, err := s.departmentRepo.GetByPath(ctx, parentPath); err == nil {
				parentID = &parent.ID
				parentName = &parent.Name
			}
		}
	}
	
	return &contract.GetDepartmentResponse{
		ID:         entity.ID,
		Name:       entity.Name,
		Code:       entity.Code,
		Path:       entity.Path,
		ParentID:   parentID,
		ParentName: parentName,
		CreatedAt:  entity.CreatedAt,
		UpdatedAt:  entity.UpdatedAt,
	}, nil
}

func (s *MasterServiceImpl) DeleteDepartment(ctx context.Context, id uuid.UUID) error {
	return s.departmentRepo.Delete(ctx, id)
}

func (s *MasterServiceImpl) GetOrganizationalChartByID(ctx context.Context, departmentID uuid.UUID) (*contract.OrganizationalChartResponse, error) {
	// First get the department to find its path
	department, err := s.departmentRepo.Get(ctx, departmentID)
	if err != nil {
		return nil, err
	}
	
	// Now get the organizational chart starting from this department's path
	return s.GetOrganizationalChart(ctx, department.Path)
}

func (s *MasterServiceImpl) GetOrganizationalChart(ctx context.Context, rootPath string) (*contract.OrganizationalChartResponse, error) {
	var departments []entities.Department
	var err error
	
	// Get config values
	parentPath := s.config.Department.ParentPath
	excludedPaths := s.config.Department.ExcludedPaths
	
	if rootPath == "" {
		// Get all departments with parent filter
		departments, err = s.departmentRepo.GetAllWithParentFilter(ctx, parentPath, excludedPaths)
	} else {
		// Get departments under specific path
		departments, err = s.departmentRepo.GetByPathPrefix(ctx, rootPath)
		// Filter out excluded paths manually for specific path queries
		filteredDepts := make([]entities.Department, 0)
		for _, dept := range departments {
			excluded := false
			for _, excludedPath := range excludedPaths {
				if strings.Contains(dept.Path, excludedPath) {
					excluded = true
					break
				}
			}
			if !excluded {
				filteredDepts = append(filteredDepts, dept)
			}
		}
		departments = filteredDepts
	}
	
	if err != nil {
		return nil, err
	}
	
	// Build the tree structure
	nodeMap := make(map[string]*contract.DepartmentNode)
	roots := make([]*contract.DepartmentNode, 0)
	
	// Calculate base level offset based on parent path
	baseLevelOffset := 0
	if parentPath != "" {
		baseLevelOffset = len(strings.Split(parentPath, ".")) - 1
	}
	
	// First pass: create all nodes including missing parents
	for _, dept := range departments {
		pathParts := strings.Split(dept.Path, ".")
		
		// Create any missing parent nodes
		for i := 1; i <= len(pathParts); i++ {
			currentPath := strings.Join(pathParts[:i], ".")
			if _, exists := nodeMap[currentPath]; !exists {
				// Calculate level for this path
				adjustedLevel := i - baseLevelOffset
				if adjustedLevel < 1 {
					adjustedLevel = 1
				}
				
				// Create node (placeholder for missing parents, real data for existing)
				var node *contract.DepartmentNode
				if currentPath == dept.Path {
					// This is the actual department
					node = &contract.DepartmentNode{
						ID:       dept.ID,
						Name:     dept.Name,
						Code:     dept.Code,
						Path:     dept.Path,
						Level:    adjustedLevel,
						Children: make([]*contract.DepartmentNode, 0),
					}
				} else {
					// This is a missing parent - create placeholder
					// Extract the last segment as the name
					lastSegment := pathParts[i-1]
					node = &contract.DepartmentNode{
						ID:       uuid.Nil, // Use nil UUID for placeholder
						Name:     strings.ToUpper(strings.ReplaceAll(lastSegment, "_", " ")),
						Code:     lastSegment,
						Path:     currentPath,
						Level:    adjustedLevel,
						Children: make([]*contract.DepartmentNode, 0),
					}
				}
				nodeMap[currentPath] = node
			}
		}
	}
	
	// Second pass: build the tree relationships
	// Only process nodes that actually exist in the database (not placeholders)
	processedPaths := make(map[string]bool)
	for _, dept := range departments {
		if processedPaths[dept.Path] {
			continue
		}
		processedPaths[dept.Path] = true
		
		node := nodeMap[dept.Path]
		pathParts := strings.Split(dept.Path, ".")
		
		// Check if this should be a root node
		isRoot := false
		if rootPath != "" && dept.Path == rootPath {
			// Explicitly requested root
			isRoot = true
		} else if rootPath == "" && parentPath != "" && dept.Path == parentPath {
			// The configured parent path is the root when showing all
			isRoot = true
		} else if len(pathParts) == 1 {
			// Single segment path
			isRoot = true
		} else {
			// Find parent path
			parentPathStr := strings.Join(pathParts[:len(pathParts)-1], ".")
			if parent, exists := nodeMap[parentPathStr]; exists {
				// Check if this child is already added
				alreadyAdded := false
				for _, child := range parent.Children {
					if child.Path == node.Path {
						alreadyAdded = true
						break
					}
				}
				if !alreadyAdded {
					parent.Children = append(parent.Children, node)
				}
			} else {
				// Parent doesn't exist - this is an orphaned node
				// Only include it as a root if it's a direct child of the parent path
				if parentPath != "" {
					// Check if this is a direct child of the configured parent
					expectedParent := parentPath
					actualParent := strings.Join(pathParts[:len(pathParts)-1], ".")
					if actualParent != expectedParent {
						// This is an orphaned node - skip it
						continue
					}
				}
				isRoot = true
			}
		}
		
		if isRoot {
			// Check for duplicates in roots
			alreadyInRoots := false
			for _, r := range roots {
				if r.Path == node.Path {
					alreadyInRoots = true
					break
				}
			}
			if !alreadyInRoots {
				roots = append(roots, node)
			}
		}
	}
	
	// Sort children at each level
	var sortChildren func([]*contract.DepartmentNode)
	sortChildren = func(nodes []*contract.DepartmentNode) {
		for _, node := range nodes {
			if len(node.Children) > 0 {
				// Sort children by name
				sort.Slice(node.Children, func(i, j int) bool {
					return node.Children[i].Name < node.Children[j].Name
				})
				sortChildren(node.Children)
			}
		}
	}
	
	// Sort root nodes
	sort.Slice(roots, func(i, j int) bool {
		return roots[i].Name < roots[j].Name
	})
	sortChildren(roots)
	
	return &contract.OrganizationalChartResponse{
		Chart:      roots,
		TotalNodes: len(departments),
	}, nil
}

func (s *MasterServiceImpl) ListDepartments(ctx context.Context, req *contract.ListDepartmentsRequest) (*contract.ListDepartmentsResponse, error) {
	// Set default values if not provided
	page := req.Page
	if page < 1 {
		page = 1
	}
	
	limit := req.Limit
	if limit < 1 {
		limit = 10
	}
	if limit > 100 {
		limit = 100 // Max limit to prevent performance issues
	}
	
	offset := (page - 1) * limit
	
	// Use filtered list with parent path from config
	parentPath := s.config.Department.ParentPath
	excludedPaths := s.config.Department.ExcludedPaths
	
	list, total, err := s.departmentRepo.ListWithParentFilter(ctx, req.Search, limit, offset, parentPath, excludedPaths)
	if err != nil {
		return nil, err
	}
	
	return &contract.ListDepartmentsResponse{
		Departments: transformer.DepartmentsToContract(list),
		Total:       total,
		Page:        page,
		Limit:       limit,
	}, nil
}
