import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { SendMessageDto } from './dto/send-message.dto';

type RichBlock =
  | { type: 'text'; data: string }
  | { type: 'highlight'; data: { color: string; text: string } }
  | { type: 'table'; data: { headers: string[]; rows: string[][] } }
  | {
      type: 'metric';
      data: { label: string; value: string; color: string }[];
    }
  | { type: 'tip'; data: string }
  | {
      type: 'checklist';
      data: { title: string; items: { text: string; checked: boolean }[] };
    };

type ChatResponse = {
  message: string;
  richContent: RichBlock[];
  stage: 1 | 2 | 3;
};

type FinancialSnapshot = {
  userName: string;
  businessName: string;
  monthlyRevenue: number;
  monthlyExpenses: number;
  netIncome: number;
  outstandingAmount: number;
  outstandingCount: number;
  fixedExpensesTotal: number;
  topClientName: string | null;
  topClientRevenue: number;
  recentMonths: { month: string; revenue: number; expenses: number }[];
};

@Injectable()
export class AiChatService {
  constructor(private readonly prisma: PrismaService) {}

  private serializeBigInt(data: any): any {
    return JSON.parse(
      JSON.stringify(data, (_, v) => (typeof v === 'bigint' ? Number(v) : v)),
    );
  }

  async getConversations(userId: number) {
    const conversations = await this.prisma.aiConversation.findMany({
      where: { userId },
      orderBy: { updatedAt: 'desc' },
      include: {
        _count: {
          select: { messages: true },
        },
      },
    });

    return this.serializeBigInt(conversations);
  }

  async getConversation(userId: number, id: number) {
    const conversation = await this.prisma.aiConversation.findFirst({
      where: { id, userId },
      include: {
        messages: {
          orderBy: { createdAt: 'asc' },
        },
      },
    });

    if (!conversation) {
      throw new NotFoundException(`Conversation with ID ${id} not found`);
    }

    return this.serializeBigInt(conversation);
  }

  async sendMessage(userId: number, dto: SendMessageDto) {
    let conversationId = dto.conversationId;

    if (!conversationId) {
      const conversation = await this.prisma.aiConversation.create({
        data: {
          userId,
          title: dto.message.substring(0, 100),
        },
      });
      conversationId = conversation.id;
    } else {
      const existing = await this.prisma.aiConversation.findFirst({
        where: { id: conversationId, userId },
      });

      if (!existing) {
        throw new NotFoundException(
          `Conversation with ID ${conversationId} not found`,
        );
      }
    }

    await this.prisma.aiMessage.create({
      data: {
        conversationId,
        role: 'user',
        content: dto.message,
      },
    });

    const response = await this.generateResponse(userId, dto.message);

    await this.prisma.aiMessage.create({
      data: {
        conversationId,
        role: 'assistant',
        content: response.message,
        richContent: response.richContent,
        stage: response.stage,
      },
    });

    await this.prisma.aiConversation.update({
      where: { id: conversationId },
      data: { updatedAt: new Date() },
    });

    return this.serializeBigInt({
      conversationId,
      message: response.message,
      richContent: response.richContent,
      stage: response.stage,
    });
  }

  async deleteConversation(userId: number, id: number) {
    const existing = await this.prisma.aiConversation.findFirst({
      where: { id, userId },
    });

    if (!existing) {
      throw new NotFoundException(`Conversation with ID ${id} not found`);
    }

    await this.prisma.aiConversation.delete({ where: { id } });

    return { message: `Conversation with ID ${id} has been deleted` };
  }

  private async generateResponse(
    userId: number,
    message: string,
  ): Promise<ChatResponse> {
    const snapshot = await this.buildFinancialSnapshot(userId);
    const normalized = message.toLowerCase();

    if (
      normalized.includes('vat') ||
      normalized.includes('부가세') ||
      normalized.includes('부가가치세')
    ) {
      return this.buildVatResponse(snapshot);
    }

    if (
      normalized.includes('cash flow') ||
      normalized.includes('현금흐름') ||
      normalized.includes('미수금') ||
      normalized.includes('입금')
    ) {
      return this.buildCashFlowResponse(snapshot);
    }

    if (
      normalized.includes('estimate') ||
      normalized.includes('quote') ||
      normalized.includes('견적') ||
      normalized.includes('contract') ||
      normalized.includes('계약')
    ) {
      return this.buildDocumentResponse(snapshot);
    }

    return this.buildGeneralResponse(snapshot);
  }

  private async buildFinancialSnapshot(
    userId: number,
  ): Promise<FinancialSnapshot> {
    const now = new Date();
    const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
    const monthEnd = new Date(
      now.getFullYear(),
      now.getMonth() + 1,
      0,
      23,
      59,
      59,
      999,
    );
    const recentMonths = Array.from({ length: 4 }, (_, index) => {
      const date = new Date(now.getFullYear(), now.getMonth() - (3 - index), 1);
      return {
        key: `${date.getFullYear()}-${date.getMonth()}`,
        month: `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}`,
        start: new Date(date.getFullYear(), date.getMonth(), 1),
        end: new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999),
      };
    });

    const [user, business, paidSettlements, outstandingSettlements, expenses, fixedExpenses] =
      await Promise.all([
        this.prisma.user.findUnique({
          where: { id: userId },
          select: { name: true },
        }),
        this.prisma.business.findUnique({
          where: { userId },
          select: { businessName: true },
        }),
        this.prisma.settlement.findMany({
          where: {
            userId,
            paymentStatus: 'paid',
            settlementDate: {
              gte: recentMonths[0].start,
              lte: recentMonths[recentMonths.length - 1].end,
            },
          },
          select: {
            totalAmount: true,
            settlementDate: true,
            client: {
              select: { name: true },
            },
          },
        }),
        this.prisma.settlement.findMany({
          where: {
            userId,
            paymentStatus: { not: 'paid' },
          },
          select: { totalAmount: true },
        }),
        this.prisma.expense.findMany({
          where: {
            userId,
            expenseDate: {
              gte: recentMonths[0].start,
              lte: recentMonths[recentMonths.length - 1].end,
            },
          },
          select: {
            amount: true,
            expenseDate: true,
          },
        }),
        this.prisma.fixedExpense.findMany({
          where: {
            userId,
            isActive: true,
          },
          select: { amount: true },
        }),
      ]);

    const monthlyRevenue = paidSettlements
      .filter(
        (settlement) =>
          new Date(settlement.settlementDate) >= monthStart &&
          new Date(settlement.settlementDate) <= monthEnd,
      )
      .reduce((sum, settlement) => sum + Number(settlement.totalAmount), 0);

    const monthlyExpenses = expenses
      .filter(
        (expense) =>
          new Date(expense.expenseDate) >= monthStart &&
          new Date(expense.expenseDate) <= monthEnd,
      )
      .reduce((sum, expense) => sum + Number(expense.amount), 0);

    const topClientMap = new Map<string, number>();
    for (const settlement of paidSettlements) {
      const clientName = settlement.client?.name || '고객';
      topClientMap.set(
        clientName,
        (topClientMap.get(clientName) || 0) + Number(settlement.totalAmount),
      );
    }

    const [topClientName = null, topClientRevenue = 0] =
      Array.from(topClientMap.entries()).sort((a, b) => b[1] - a[1])[0] || [];

    return {
      userName: user?.name || '고객님',
      businessName: business?.businessName || '내 사업장',
      monthlyRevenue,
      monthlyExpenses,
      netIncome: monthlyRevenue - monthlyExpenses,
      outstandingAmount: outstandingSettlements.reduce(
        (sum, settlement) => sum + Number(settlement.totalAmount),
        0,
      ),
      outstandingCount: outstandingSettlements.length,
      fixedExpensesTotal: fixedExpenses.reduce(
        (sum, expense) => sum + Number(expense.amount),
        0,
      ),
      topClientName,
      topClientRevenue,
      recentMonths: recentMonths.map((monthInfo) => ({
        month: monthInfo.month,
        revenue: paidSettlements
          .filter(
            (settlement) =>
              new Date(settlement.settlementDate) >= monthInfo.start &&
              new Date(settlement.settlementDate) <= monthInfo.end,
          )
          .reduce((sum, settlement) => sum + Number(settlement.totalAmount), 0),
        expenses: expenses
          .filter(
            (expense) =>
              new Date(expense.expenseDate) >= monthInfo.start &&
              new Date(expense.expenseDate) <= monthInfo.end,
          )
          .reduce((sum, expense) => sum + Number(expense.amount), 0),
      })),
    };
  }

  private buildVatResponse(snapshot: FinancialSnapshot): ChatResponse {
    const outputVat = Math.round(snapshot.monthlyRevenue * 0.1);
    const inputVat = Math.round(snapshot.monthlyExpenses * 0.1);
    const estimatedVat = Math.max(outputVat - inputVat, 0);

    return {
      message:
        '이번 달 매출과 비용 기준으로 예상 부가세를 정리했습니다.',
      stage: 1,
      richContent: [
        {
          type: 'text',
          data: `${snapshot.userName}님의 최근 데이터 기준으로 예상 부가세를 계산했습니다.`,
        },
        {
          type: 'table',
          data: {
            headers: ['항목', '금액'],
            rows: [
              ['매출세액', this.formatCurrency(outputVat)],
              ['매입세액', this.formatCurrency(inputVat)],
              ['예상 납부세액', this.formatCurrency(estimatedVat)],
            ],
          },
        },
        {
          type: 'tip',
          data:
            '카드 연동과 비용 증빙 정리가 잘 되어 있을수록 매입세액 반영이 정확해집니다.',
        },
      ],
    };
  }

  private buildCashFlowResponse(snapshot: FinancialSnapshot): ChatResponse {
    return {
      message:
        '현금흐름과 미수금 관점에서 지금 바로 확인할 포인트를 정리했습니다.',
      stage: 1,
      richContent: [
        {
          type: 'metric',
          data: [
            {
              label: '이번 달 매출',
              value: this.formatCurrency(snapshot.monthlyRevenue),
              color: '#3B82F6',
            },
            {
              label: '이번 달 비용',
              value: this.formatCurrency(snapshot.monthlyExpenses),
              color: '#F59E0B',
            },
            {
              label: '순이익',
              value: this.formatCurrency(snapshot.netIncome),
              color: '#10B981',
            },
          ],
        },
        {
          type: 'table',
          data: {
            headers: ['월', '매출', '비용', '순이익'],
            rows: snapshot.recentMonths.map((month) => [
              month.month,
              this.formatCurrency(month.revenue),
              this.formatCurrency(month.expenses),
              this.formatCurrency(month.revenue - month.expenses),
            ]),
          },
        },
        {
          type: 'highlight',
          data: {
            color: '#EF4444',
            text:
              snapshot.outstandingCount > 0
                ? `미수 정산 ${snapshot.outstandingCount}건, ${this.formatCurrency(snapshot.outstandingAmount)}이 남아 있습니다.`
                : '현재 미수 정산은 없습니다.',
          },
        },
      ],
    };
  }

  private buildDocumentResponse(snapshot: FinancialSnapshot): ChatResponse {
    const recommendedAmount =
      snapshot.topClientRevenue > 0
        ? Math.round(snapshot.topClientRevenue * 1.08)
        : Math.max(snapshot.monthlyRevenue, 500000);

    return {
      message:
        '최근 거래 규모를 기준으로 문서 작성 시 참고할 포인트를 정리했습니다.',
      stage: 2,
      richContent: [
        {
          type: 'text',
          data: `${snapshot.businessName}의 실제 매출 흐름을 기준으로 추천 범위를 계산했습니다.`,
        },
        {
          type: 'metric',
          data: [
            {
              label: '추천 견적 기준',
              value: this.formatCurrency(recommendedAmount),
              color: '#8B5CF6',
            },
            {
              label: '고정비',
              value: this.formatCurrency(snapshot.fixedExpensesTotal),
              color: '#6B7280',
            },
          ],
        },
        {
          type: 'checklist',
          data: {
            title: '계약서 체크리스트',
            items: [
              { text: '작업 범위와 산출물 명시', checked: true },
              { text: '수정 횟수와 초과 비용 조건', checked: true },
              { text: '선금과 잔금 지급 일정', checked: true },
              { text: '세금계산서 발행 시점 정의', checked: true },
            ],
          },
        },
      ],
    };
  }

  private buildGeneralResponse(snapshot: FinancialSnapshot): ChatResponse {
    const guidance =
      snapshot.topClientName && snapshot.topClientRevenue > 0
        ? `가장 큰 매출처는 ${snapshot.topClientName}이며 누적 ${this.formatCurrency(snapshot.topClientRevenue)} 매출이 발생했습니다.`
        : '아직 누적 매출 데이터가 많지 않아 기본 가이드 중심으로 안내합니다.';

    return {
      message:
        '현재 사업 데이터를 기준으로 우선 확인하면 좋은 재무 포인트를 정리했습니다.',
      stage: 3,
      richContent: [
        {
          type: 'text',
          data: `${snapshot.userName}님의 최신 사업 데이터를 기반으로 요약했습니다.`,
        },
        {
          type: 'metric',
          data: [
            {
              label: '이번 달 매출',
              value: this.formatCurrency(snapshot.monthlyRevenue),
              color: '#3B82F6',
            },
            {
              label: '미수 잔액',
              value: this.formatCurrency(snapshot.outstandingAmount),
              color: '#EF4444',
            },
            {
              label: '고정비',
              value: this.formatCurrency(snapshot.fixedExpensesTotal),
              color: '#6B7280',
            },
          ],
        },
        {
          type: 'highlight',
          data: {
            color: '#F59E0B',
            text: guidance,
          },
        },
        {
          type: 'tip',
          data:
            '매출, 비용, 세금, 견적 중 궁금한 주제를 구체적으로 물어보면 더 정확한 분석으로 이어집니다.',
        },
      ],
    };
  }

  private formatCurrency(value: number) {
    return `₩${Math.max(Math.round(value), 0).toLocaleString('ko-KR')}`;
  }
}
