package service

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/logging"
	"github.com/orchard9/rdev/internal/port"
)

// ArchitectService orchestrates conversational project design with Claude.
type ArchitectService struct {
	conversationService *ConversationService
	blueprintService    *BlueprintService
	agentRegistry       port.CodeAgentRegistry
	projectRepo         port.ProjectRepository
}

// NewArchitectService creates a new architect service.
func NewArchitectService(
	conversationService *ConversationService,
	blueprintService *BlueprintService,
	agentRegistry port.CodeAgentRegistry,
	projectRepo port.ProjectRepository,
) *ArchitectService {
	return &ArchitectService{
		conversationService: conversationService,
		blueprintService:    blueprintService,
		agentRegistry:       agentRegistry,
		projectRepo:         projectRepo,
	}
}

// StartConversation begins a new architectural conversation.
func (s *ArchitectService) StartConversation(ctx context.Context, projectID, initialPrompt string) (*domain.ConversationWithMessages, error) {
	// Create conversation
	conv, err := s.conversationService.CreateConversation(ctx, projectID, "Architectural Design")
	if err != nil {
		return nil, fmt.Errorf("create conversation: %w", err)
	}

	// Add user's initial message
	_, err = s.conversationService.AddMessage(ctx, conv.ID, domain.MessageRoleUser, initialPrompt)
	if err != nil {
		return nil, fmt.Errorf("add user message: %w", err)
	}

	// Get agent response
	response, err := s.askArchitect(ctx, projectID, conv.ID, initialPrompt)
	if err != nil {
		return nil, fmt.Errorf("ask architect: %w", err)
	}

	// Add assistant's response
	_, err = s.conversationService.AddMessage(ctx, conv.ID, domain.MessageRoleAssistant, response)
	if err != nil {
		return nil, fmt.Errorf("add assistant message: %w", err)
	}

	log := logging.FromContext(ctx)
	log.Info("architectural conversation started",
		"conversation_id", conv.ID,
		logging.FieldProjectID, projectID,
		logging.FieldOperation, "start_architect_conversation",
	)

	return s.conversationService.GetConversationWithMessages(ctx, conv.ID)
}

// ContinueConversation adds a message and gets agent response.
func (s *ArchitectService) ContinueConversation(ctx context.Context, conversationID domain.ConversationID, userMessage string) (*domain.Message, error) {
	// Get conversation to find project
	conv, err := s.conversationService.GetConversation(ctx, conversationID)
	if err != nil {
		return nil, err
	}

	// Add user message
	_, err = s.conversationService.AddMessage(ctx, conversationID, domain.MessageRoleUser, userMessage)
	if err != nil {
		return nil, fmt.Errorf("add user message: %w", err)
	}

	// Get conversation history for context
	messages, err := s.conversationService.GetMessages(ctx, conversationID)
	if err != nil {
		return nil, err
	}

	// Build context from history
	context := s.buildConversationContext(messages)

	// Get agent response
	response, err := s.askArchitect(ctx, conv.ProjectID, conversationID, context)
	if err != nil {
		return nil, fmt.Errorf("ask architect: %w", err)
	}

	// Add assistant's response
	return s.conversationService.AddMessage(ctx, conversationID, domain.MessageRoleAssistant, response)
}

// GenerateBlueprint creates a blueprint from a conversation.
func (s *ArchitectService) GenerateBlueprint(ctx context.Context, conversationID domain.ConversationID, blueprintName string) (*domain.Blueprint, error) {
	conv, err := s.conversationService.GetConversation(ctx, conversationID)
	if err != nil {
		return nil, err
	}

	messages, err := s.conversationService.GetMessages(ctx, conversationID)
	if err != nil {
		return nil, err
	}

	// Extract structured spec from conversation
	spec, err := s.extractSpecFromMessages(ctx, conv.ProjectID, messages)
	if err != nil {
		return nil, fmt.Errorf("extract spec: %w", err)
	}

	// Create blueprint
	return s.blueprintService.CreateBlueprint(ctx, conv.ProjectID, blueprintName, "Generated from architectural conversation", spec)
}

// askArchitect sends a prompt to Claude and gets the response.
func (s *ArchitectService) askArchitect(ctx context.Context, projectID string, conversationID domain.ConversationID, prompt string) (string, error) {
	agent := s.agentRegistry.Default()
	if agent == nil {
		return "", fmt.Errorf("no agent available")
	}

	project, err := s.projectRepo.Get(ctx, domain.ProjectID(projectID))
	if err != nil {
		return "", fmt.Errorf("resolve project: %w", err)
	}

	// Prepare architect-specific system prompt
	systemPrompt := `You are an expert software architect helping design a new project.

Your role is to:
1. Ask clarifying questions to understand requirements deeply
2. Identify technical architecture and stack choices
3. Recommend component structure (monorepo, microservices, etc.)
4. Define infrastructure needs (database, cache, storage, messaging)
5. Consider scalability, maintainability, and team capabilities

Guidelines:
- Ask one focused question at a time
- Provide specific recommendations with trade-offs
- Think about the full system: data models, APIs, UI components, infrastructure
- When you have enough information, summarize the architecture clearly

Current conversation context:`

	fullPrompt := systemPrompt + "\n\n" + prompt

	agentReq := &domain.AgentRequest{
		Prompt:    fullPrompt,
		ProjectID: project.ID,
		Timeout:   2 * time.Minute,
		Metadata: map[string]string{
			"conversation_id": string(conversationID),
			"purpose":         "architect",
		},
	}

	var output strings.Builder
	_, err = agent.Execute(ctx, agentReq, func(event domain.AgentEvent) {
		if event.Type == domain.AgentEventOutput {
			output.WriteString(event.Content)
		}
	})
	if err != nil {
		return "", fmt.Errorf("agent execution: %w", err)
	}

	return output.String(), nil
}

// buildConversationContext combines message history into a single context string.
func (s *ArchitectService) buildConversationContext(messages []*domain.Message) string {
	var context strings.Builder
	for _, msg := range messages {
		role := string(msg.Role)
		context.WriteString(fmt.Sprintf("%s: %s\n\n", role, msg.Content))
	}
	return context.String()
}

// extractSpecFromMessages parses conversation to extract structured blueprint spec.
func (s *ArchitectService) extractSpecFromMessages(ctx context.Context, projectID string, messages []*domain.Message) (map[string]any, error) {
	// Use Claude to analyze conversation and extract structured data
	agent := s.agentRegistry.Default()
	if agent == nil {
		return nil, fmt.Errorf("no agent available")
	}

	project, err := s.projectRepo.Get(ctx, domain.ProjectID(projectID))
	if err != nil {
		return nil, fmt.Errorf("resolve project: %w", err)
	}

	// Build conversation transcript
	transcript := s.buildConversationContext(messages)

	// Prompt to extract structured spec
	extractionPrompt := fmt.Sprintf(`Analyze this architectural conversation and extract a structured JSON specification.

Conversation transcript:
%s

Extract and return ONLY a valid JSON object with this structure:
{
  "version": "1.0",
  "architecture": {
    "type": "monorepo|microservices|monolith",
    "description": "...",
    "components": [
      {"name": "...", "type": "service|app|worker|cli", "description": "..."}
    ]
  },
  "data_models": [
    {"name": "...", "description": "...", "fields": [...]}
  ],
  "api_endpoints": [
    {"path": "...", "method": "GET|POST|PUT|DELETE", "description": "..."}
  ],
  "infrastructure": {
    "database": {"type": "postgres|mysql|mongo", "required": true|false},
    "cache": {"type": "redis|memcached", "required": true|false},
    "storage": {"type": "s3|gcs", "required": true|false},
    "messaging": {"type": "rabbitmq|kafka", "required": true|false}
  },
  "features": [
    {"name": "...", "description": "...", "priority": "high|medium|low"}
  ]
}

Return ONLY the JSON, no other text.`, transcript)

	agentReq := &domain.AgentRequest{
		Prompt:    extractionPrompt,
		ProjectID: project.ID,
		Timeout:   2 * time.Minute,
		Metadata: map[string]string{
			"purpose": "spec-extraction",
		},
	}

	var output strings.Builder
	_, err = agent.Execute(ctx, agentReq, func(event domain.AgentEvent) {
		if event.Type == domain.AgentEventOutput {
			output.WriteString(event.Content)
		}
	})
	if err != nil {
		return nil, fmt.Errorf("agent execution: %w", err)
	}

	// Parse JSON response
	response := output.String()

	// Extract JSON from markdown code blocks if present
	if strings.Contains(response, "```json") {
		start := strings.Index(response, "```json") + 7
		end := strings.Index(response[start:], "```")
		if end != -1 {
			response = response[start : start+end]
		}
	} else if strings.Contains(response, "```") {
		start := strings.Index(response, "```") + 3
		end := strings.Index(response[start:], "```")
		if end != -1 {
			response = response[start : start+end]
		}
	}

	response = strings.TrimSpace(response)

	var spec map[string]any
	if err := json.Unmarshal([]byte(response), &spec); err != nil {
		// Fallback: create basic spec if parsing fails
		log := logging.FromContext(ctx)
		log.Warn("failed to parse agent spec extraction, using fallback",
			logging.FieldError, err,
			logging.FieldOperation, "extract_blueprint_spec",
			"response_length", len(response),
			"message_count", len(messages),
		)
		spec = map[string]any{
			"version":      "1.0",
			"generated_at": time.Now().Format(time.RFC3339),
			"message_count": len(messages),
			"extraction_failed": true,
		}
	}

	return spec, nil
}
