import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {
  ConversationEntity,
  ConversationStatus,
} from '../domain/entities/conversation.entity';
import {
  MessageEntity,
  MessageRole,
  MessageType,
} from '../domain/entities/message.entity';
import {
  ClaudeAgentServiceV2,
  ConversationContext,
  StreamChunk,
} from '../infrastructure/claude/claude-agent-v2.service';

export interface CreateConversationDto {
  userId: string;
  title?: string;
}

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

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

@Injectable()
export class ConversationService {
  constructor(
    @InjectRepository(ConversationEntity)
    private conversationRepo: Repository<ConversationEntity>,
    @InjectRepository(MessageEntity)
    private messageRepo: Repository<MessageEntity>,
    private claudeAgentService: ClaudeAgentServiceV2,
  ) {}

  /**
   * Create a new conversation
   */
  async createConversation(dto: CreateConversationDto): Promise<ConversationEntity> {
    const conversation = this.conversationRepo.create({
      userId: dto.userId,
      title: dto.title || '新对话',
      status: ConversationStatus.ACTIVE,
    });

    return this.conversationRepo.save(conversation);
  }

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

    if (!conversation) {
      throw new NotFoundException('Conversation not found');
    }

    return conversation;
  }

  /**
   * Get user's conversations
   */
  async getUserConversations(userId: string): Promise<ConversationEntity[]> {
    return this.conversationRepo.find({
      where: { userId },
      order: { updatedAt: 'DESC' },
    });
  }

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

    return this.messageRepo.find({
      where: { conversationId },
      order: { createdAt: 'ASC' },
    });
  }

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

    if (conversation.status !== ConversationStatus.ACTIVE) {
      throw new Error('Conversation is not active');
    }

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

    // Get previous messages for context
    const previousMessages = await this.messageRepo.find({
      where: { conversationId: dto.conversationId },
      order: { createdAt: 'ASC' },
      take: 20, // Last 20 messages for context
    });

    // Build context with support for multimodal messages and consulting state (V2)
    const context: ConversationContext = {
      userId: dto.userId,
      conversationId: dto.conversationId,
      previousMessages: previousMessages.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,
    };

    // Collect full response for saving
    let fullResponse = '';
    const toolCalls: Array<{ name: string; input: unknown; result: unknown }> = [];
    let updatedState: ConversationContext['consultingState'] | undefined;

    // Stream response from Claude (with attachments for multimodal support)
    for await (const chunk of this.claudeAgentService.sendMessage(
      dto.content,
      context,
      dto.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;
      }

      yield chunk;
    }

    // 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));
      await this.conversationRepo.update(conversation.id, {
        consultingState: stateForDb,
        consultingStage: updatedState.currentStageId,
        collectedInfo: stateForDb.collectedInfo,
        recommendedPrograms: updatedState.assessmentResult?.recommendedPrograms,
        conversionPath: updatedState.conversionPath,
      });
    }

    // Save assistant response
    const assistantMessage = this.messageRepo.create({
      conversationId: dto.conversationId,
      role: MessageRole.ASSISTANT,
      type: MessageType.TEXT,
      content: fullResponse,
      metadata: toolCalls.length > 0 ? { toolCalls } : undefined,
    });
    await this.messageRepo.save(assistantMessage);

    // Update conversation title if first message
    if (conversation.messageCount === 0) {
      const title = await this.generateTitle(dto.content);
      await this.conversationRepo.update(conversation.id, { title });
    }
  }

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

    await this.conversationRepo.update(conversation.id, {
      status: ConversationStatus.ENDED,
      endedAt: new Date(),
    });
  }

  /**
   * Delete a conversation and its messages
   */
  async deleteConversation(conversationId: string, userId: string): Promise<void> {
    // Verify user owns the conversation
    const conversation = await this.getConversation(conversationId, userId);

    // Delete messages first (due to foreign key constraint)
    await this.messageRepo.delete({ conversationId: conversation.id });

    // Delete conversation
    await this.conversationRepo.delete(conversation.id);
  }

  /**
   * 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;
  }
}
