package service

import (
	"context"
	"database/sql"
	"encoding/base64"
	"fmt"
	"path/filepath"
	"strings"

	giteaadapter "github.com/orchard9/rdev/internal/adapter/gitea"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/logging"
)

// ListComponents lists all components in a project's monorepo.
func (s *ComponentService) ListComponents(ctx context.Context, projectID string) ([]domain.Component, error) {
	// Get project info from database
	var gitRepoOwner, gitRepoName string
	err := s.db.QueryRowContext(ctx, `
		SELECT COALESCE(git_repo_owner, $2), COALESCE(git_repo_name, $1)
		FROM projects WHERE id = $1
	`, projectID, s.defaultGitOwner).Scan(&gitRepoOwner, &gitRepoName)
	if err == sql.ErrNoRows {
		return nil, fmt.Errorf("%w: %s", domain.ErrProjectNotFound, projectID)
	}
	if err != nil {
		return nil, fmt.Errorf("failed to get project: %w", err)
	}

	// Read Procfile once (not inside the loop)
	procfileContent, _, err := s.bulkClient.GetFileContent(ctx, gitRepoOwner, gitRepoName, "Procfile")
	if err != nil {
		log := logging.FromContext(ctx).WithService("component")
		log.Warn("failed to read Procfile", logging.FieldError, err)
		return []domain.Component{}, nil
	}
	if procfileContent == nil {
		return []domain.Component{}, nil
	}

	var components []domain.Component
	procfileStr := string(procfileContent)

	// Check each component type's directory
	for _, ct := range domain.ValidComponentTypes {
		destDir := ct.DestDir()
		if destDir == "" {
			continue
		}

		// Parse Procfile to extract component info for this type
		comps := s.parseComponentsFromProcfile(procfileStr, ct)
		components = append(components, comps...)
	}

	return components, nil
}

// parseComponentsFromProcfile extracts component information from a Procfile.
// Ports are assigned incrementally based on discovery order within each component type.
func (s *ComponentService) parseComponentsFromProcfile(procfile string, componentType domain.ComponentType) []domain.Component {
	var components []domain.Component

	// Use pre-compiled pattern from package-level map
	pattern, ok := procfilePatterns[componentType]
	if !ok {
		return components
	}

	startingPort := componentType.StartingPort()
	portOffset := 0

	for _, line := range strings.Split(procfile, "\n") {
		matches := pattern.FindStringSubmatch(strings.TrimSpace(line))
		if len(matches) >= 3 {
			name := matches[1]
			path := matches[2]

			// Assign ports incrementally based on discovery order
			port := 0
			if componentType.NeedsPort() {
				port = startingPort + portOffset
				portOffset++
			}

			components = append(components, domain.Component{
				Type:         componentType,
				Name:         name,
				Path:         path,
				Port:         port,
				Template:     string(componentType),
				Dependencies: []string{},
			})
		}
	}

	return components
}

// RemoveComponent removes a component from a project's monorepo.
func (s *ComponentService) RemoveComponent(ctx context.Context, projectID string, componentPath string) error {
	// Get project info from database
	var gitRepoOwner, gitRepoName string
	err := s.db.QueryRowContext(ctx, `
		SELECT COALESCE(git_repo_owner, $2), COALESCE(git_repo_name, $1)
		FROM projects WHERE id = $1
	`, projectID, s.defaultGitOwner).Scan(&gitRepoOwner, &gitRepoName)
	if err == sql.ErrNoRows {
		return fmt.Errorf("%w: %s", domain.ErrProjectNotFound, projectID)
	}
	if err != nil {
		return fmt.Errorf("failed to get project: %w", err)
	}

	// Verify component exists by checking for a file in the path
	checkFile := componentPath + "/go.mod"
	content, _, err := s.bulkClient.GetFileContent(ctx, gitRepoOwner, gitRepoName, checkFile)
	if err != nil {
		return fmt.Errorf("failed to check component: %w", err)
	}
	if content == nil {
		// Try package.json for frontend apps
		checkFile = componentPath + "/package.json"
		content, _, err = s.bulkClient.GetFileContent(ctx, gitRepoOwner, gitRepoName, checkFile)
		if err != nil {
			return fmt.Errorf("failed to check component: %w", err)
		}
		if content == nil {
			return fmt.Errorf("%w: %s", domain.ErrComponentNotFound, componentPath)
		}
	}

	// Extract component name from path
	componentName := filepath.Base(componentPath)

	// Determine component type from path
	var componentType domain.ComponentType
	switch {
	case strings.HasPrefix(componentPath, "services/"):
		componentType = domain.ComponentTypeService
	case strings.HasPrefix(componentPath, "workers/"):
		componentType = domain.ComponentTypeWorker
	case strings.HasPrefix(componentPath, "apps/"):
		// Could be astro or react - check package.json later
		componentType = domain.ComponentTypeAppAstro
	case strings.HasPrefix(componentPath, "cli/"):
		componentType = domain.ComponentTypeCLI
	default:
		return fmt.Errorf("unknown component path structure: %s", componentPath)
	}

	// For now, we'll update the monorepo files to remove references
	// Actual file deletion would require listing all files in the directory
	// which the Gitea API doesn't support easily

	var fileOps []giteaadapter.ChangeFileOperation

	// 1. Update Procfile - remove the component entry
	procfileContent, procfileSHA, err := s.bulkClient.GetFileContent(ctx, gitRepoOwner, gitRepoName, "Procfile")
	if err != nil {
		return fmt.Errorf("failed to get Procfile: %w", err)
	}
	if procfileContent != nil {
		updated := s.removeProcfileEntry(string(procfileContent), componentName)
		fileOps = append(fileOps, giteaadapter.ChangeFileOperation{
			Operation: "update",
			Path:      "Procfile",
			Content:   base64.StdEncoding.EncodeToString([]byte(updated)),
			SHA:       procfileSHA,
		})
	}

	// 2. Update go.work if Go component
	if componentType.IsGoComponent() {
		goWorkContent, goWorkSHA, err := s.bulkClient.GetFileContent(ctx, gitRepoOwner, gitRepoName, "go.work")
		if err != nil {
			return fmt.Errorf("failed to get go.work: %w", err)
		}
		if goWorkContent != nil {
			updated := s.removeGoWorkEntry(string(goWorkContent), componentPath)
			fileOps = append(fileOps, giteaadapter.ChangeFileOperation{
				Operation: "update",
				Path:      "go.work",
				Content:   base64.StdEncoding.EncodeToString([]byte(updated)),
				SHA:       goWorkSHA,
			})
		}
	}

	// Note: Removing from .woodpecker.yml and CLAUDE.md is more complex
	// because we'd need to parse YAML and markdown tables properly.
	// For now, we'll leave those as manual cleanup tasks.

	if len(fileOps) > 0 {
		opts := giteaadapter.ChangeFilesOptions{
			Files:   fileOps,
			Message: fmt.Sprintf("Remove component references: %s", componentName),
		}

		_, err = s.bulkClient.ChangeFiles(ctx, gitRepoOwner, gitRepoName, opts)
		if err != nil {
			return fmt.Errorf("failed to commit changes: %w", err)
		}
	}

	log := logging.FromContext(ctx).WithService("component")
	log.Info("component removed",
		logging.FieldProjectID, projectID,
		"path", componentPath,
		"note", "Component files remain in repo - delete manually if needed",
	)

	return nil
}
