import { Injectable, Inject, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { v4 as uuidv4 } from 'uuid';
import { TenantContextService } from '@iconsulting/shared';
import {
  ConversationEntity,
  ConversationStatus,
  DeviceInfo,
} from '../../domain/entities/conversation.entity';
import {
  MessageEntity,
  MessageRole,
  MessageType,
} from '../../domain/entities/message.entity';
import {
  IConversationRepository,
  CONVERSATION_REPOSITORY,
} from '../../domain/repositories/conversation.repository.interface';
import {
  IMessageRepository,
  MESSAGE_REPOSITORY,
} from '../../domain/repositories/message.repository.interface';
import {
  CoordinatorAgentService,
  LegacyConversationContext as ConversationContext,
  StreamChunk,
} from '../../infrastructure/agents/coordinator/coordinator-agent.service';
import { AgentExecutionORM } from '../../infrastructure/database/postgres/entities/agent-execution.orm';

export interface CreateConversationParams {
  userId: string;
  title?: string;
  deviceInfo?: DeviceInfo;
}

export interface FileAttachment {
  id: string;
  originalName: string;
  mimeType: string;
  type: 'image' | 'document' | 'audio' | 'video' | 'other';
  size: number;
  downloadUrl?: string;
  thumbnailUrl?: string;
}

export interface SendMessageParams {
  conversationId: string;
  userId: string;
  content: string;
  attachments?: FileAttachment[];
}

@Injectable()
export class ConversationService {
  constructor(
    @Inject(CONVERSATION_REPOSITORY)
    private readonly conversationRepo: IConversationRepository,
    @Inject(MESSAGE_REPOSITORY)
    private readonly messageRepo: IMessageRepository,
    private readonly claudeAgentService: CoordinatorAgentService,
    @InjectRepository(AgentExecutionORM)
    private readonly agentExecutionRepo: Repository<AgentExecutionORM>,
    private readonly tenantContext: TenantContextService,
  ) {}

  /**
   * Create a new conversation
   */
  async createConversation(params: CreateConversationParams): Promise<ConversationEntity> {
    const conversation = ConversationEntity.create({
      id: uuidv4(),
      userId: params.userId,
      title: params.title || '新对话',
      deviceInfo: params.deviceInfo,
    });

    return this.conversationRepo.save(conversation);
  }

  /**
   * Get conversation by ID
   */
  async getConversation(
    conversationId: string,
    userId: string,
  ): Promise<ConversationEntity> {
    const conversation = await this.conversationRepo.findById(conversationId);

    if (!conversation || conversation.userId !== userId || conversation.isDeleted()) {
      throw new NotFoundException('Conversation not found');
    }

    return conversation;
  }

  /**
   * Get user's conversations
   */
  async getUserConversations(userId: string): Promise<ConversationEntity[]> {
    return this.conversationRepo.findByUserId(userId);
  }

  /**
   * Get conversation messages
   */
  async getMessages(
    conversationId: string,
    userId: string,
  ): Promise<MessageEntity[]> {
    // Verify user owns the conversation
    await this.getConversation(conversationId, userId);

    return this.messageRepo.findByConversationId(conversationId);
  }

  /**
   * Send a message and get streaming response
   */
  async *sendMessage(params: SendMessageParams): AsyncGenerator<StreamChunk> {
    // Verify conversation exists and belongs to user
    const conversation = await this.getConversation(params.conversationId, params.userId);

    if (!conversation.isActive()) {
      throw new Error('Conversation is not active');
    }

    // Save user message with attachments if present
    const hasAttachments = params.attachments && params.attachments.length > 0;
    const userMessage = MessageEntity.create({
      id: uuidv4(),
      conversationId: params.conversationId,
      role: MessageRole.USER,
      type: hasAttachments ? MessageType.TEXT_WITH_ATTACHMENTS : MessageType.TEXT,
      content: params.content,
      metadata: hasAttachments ? { attachments: params.attachments } : undefined,
    });
    await this.messageRepo.save(userMessage);

    // Get previous messages for context
    const previousMessages = await this.messageRepo.findByConversationId(params.conversationId);
    const recentMessages = previousMessages.slice(-20); // Last 20 messages for context

    // Build context with support for multimodal messages and consulting state (V2)
    const context: ConversationContext = {
      userId: params.userId,
      conversationId: params.conversationId,
      previousMessages: recentMessages.map((m) => {
        const msg: { role: 'user' | 'assistant'; content: string; attachments?: FileAttachment[] } = {
          role: m.role as 'user' | 'assistant',
          content: m.content,
        };
        // Include attachments from metadata if present
        if (m.metadata?.attachments) {
          msg.attachments = m.metadata.attachments as FileAttachment[];
        }
        return msg;
      }),
      // V2: Pass consulting state from conversation (cast through unknown for JSON/Date compatibility)
      consultingState: conversation.consultingState as unknown as ConversationContext['consultingState'],
      deviceInfo: conversation.deviceInfo || undefined,
    };

    // Collect full response for saving
    let fullResponse = '';
    const toolCalls: Array<{ name: string; input: unknown; result: unknown }> = [];
    let updatedState: ConversationContext['consultingState'] | undefined;
    let inputTokens = 0;
    let outputTokens = 0;
    let streamError: Error | null = null;
    const agentExecutions: Array<{
      agentType: string;
      agentName: string;
      startedAt: number;
      durationMs?: number;
      success?: boolean;
    }> = [];

    // Stream response from Claude (with attachments for multimodal support)
    try {
      for await (const chunk of this.claudeAgentService.sendMessage(
        params.content,
        context,
        params.attachments,
      )) {
        if (chunk.type === 'text' && chunk.content) {
          fullResponse += chunk.content;
        } else if (chunk.type === 'tool_use') {
          toolCalls.push({
            name: chunk.toolName!,
            input: chunk.toolInput!,
            result: null,
          });
        } else if (chunk.type === 'tool_result') {
          const lastToolCall = toolCalls[toolCalls.length - 1];
          if (lastToolCall) {
            lastToolCall.result = chunk.toolResult;
          }
        } else if (chunk.type === 'state_update' && chunk.newState) {
          // V2: Capture updated consulting state
          updatedState = chunk.newState;
        } else if (chunk.type === 'end') {
          // Capture token usage from end chunk
          inputTokens = chunk.inputTokens || 0;
          outputTokens = chunk.outputTokens || 0;
        } else if (chunk.type === 'error') {
          // Capture partial token usage from error chunk
          inputTokens = chunk.inputTokens || 0;
          outputTokens = chunk.outputTokens || 0;
          console.warn(`[ConversationService] Stream error, captured partial tokens: in=${inputTokens}, out=${outputTokens}`);
        } else if (chunk.type === 'agent_start' && chunk.agentType) {
          agentExecutions.push({
            agentType: chunk.agentType,
            agentName: chunk.agentName || chunk.agentType,
            startedAt: Date.now(),
          });
        } else if (chunk.type === 'agent_complete' && chunk.agentType) {
          const exec = agentExecutions.find(
            e => e.agentType === chunk.agentType && e.durationMs === undefined,
          );
          if (exec) {
            exec.durationMs = chunk.durationMs ?? (Date.now() - exec.startedAt);
            exec.success = chunk.success ?? true;
          }
        }

        yield chunk;
      }
    } catch (error) {
      // Capture the error but continue to save partial data
      streamError = error instanceof Error ? error : new Error(String(error));
      console.error('[ConversationService] Stream error:', streamError.message);
    }

    // V2: Save updated consulting state to conversation
    if (updatedState) {
      // Convert state to JSON-compatible format for database storage
      const stateForDb = JSON.parse(JSON.stringify(updatedState));
      conversation.updateConsultingState(stateForDb);
    }

    // Save assistant response (even partial response on error)
    if (fullResponse) {
      const assistantMessage = MessageEntity.create({
        id: uuidv4(),
        conversationId: params.conversationId,
        role: MessageRole.ASSISTANT,
        type: MessageType.TEXT,
        content: fullResponse,
        metadata: toolCalls.length > 0 ? { toolCalls } : undefined,
      });
      await this.messageRepo.save(assistantMessage);
    }

    // Save agent execution records
    if (agentExecutions.length > 0) {
      try {
        const tenantId = this.tenantContext.getCurrentTenantId() || '';
        await this.agentExecutionRepo.save(
          agentExecutions.map(exec => this.agentExecutionRepo.create({
            tenantId,
            conversationId: params.conversationId,
            userId: params.userId,
            agentType: exec.agentType,
            agentName: exec.agentName,
            durationMs: exec.durationMs ?? 0,
            success: exec.success ?? true,
          })),
        );
      } catch (err) {
        console.error('[ConversationService] Failed to save agent executions:', err);
      }
    }

    // Update conversation statistics (always update tokens, even on error)
    conversation.incrementMessageCount('user');
    if (fullResponse) {
      conversation.incrementMessageCount('assistant');
    }
    conversation.addTokens(inputTokens, outputTokens);

    // Update conversation title if first message
    if (conversation.messageCount === 2) {
      // Now 2 after incrementing (user + assistant)
      const title = await this.generateTitle(params.content);
      conversation.title = title;
    }

    // Save all updates to conversation
    await this.conversationRepo.update(conversation);

    // Re-throw error after saving partial data
    if (streamError) {
      throw streamError;
    }
  }

  /**
   * End a conversation
   */
  async endConversation(conversationId: string, userId: string): Promise<void> {
    const conversation = await this.getConversation(conversationId, userId);
    conversation.end();
    await this.conversationRepo.update(conversation);
  }

  /**
   * Delete a conversation and its messages
   */
  async deleteConversation(conversationId: string, userId: string): Promise<void> {
    const conversation = await this.getConversation(conversationId, userId);
    conversation.softDelete();
    await this.conversationRepo.update(conversation);
  }

  /**
   * Generate a title from the first message
   */
  private async generateTitle(firstMessage: string): Promise<string> {
    // Simple title generation - take first 50 chars
    const title = firstMessage.substring(0, 50);
    return title.length < firstMessage.length ? `${title}...` : title;
  }
}
