import { Body, Controller, HttpException, Logger, Post, Req, Res, UseGuards } from "@nestjs/common";
import type { Response } from "express";
import { AuthGuard } from "../auth/guards/auth.guard";
import { SkipCsrf } from "../common/decorators/skip-csrf.decorator";
import type { MaybeAuthenticatedRequest } from "../auth/types/better-auth-request.interface";
import { ChatStreamDto } from "./chat-proxy.dto";
import { ChatProxyService } from "./chat-proxy.service";

@Controller("chat")
export class ChatProxyController {
  private readonly logger = new Logger(ChatProxyController.name);

  constructor(private readonly chatProxyService: ChatProxyService) {}

  // POST /api/chat/guest
  // Guest chat endpoint - no authentication required
  // Uses a shared LLM configuration for unauthenticated users
  @SkipCsrf()
  @Post("guest")
  async guestChat(
    @Body() body: ChatStreamDto,
    @Req() req: MaybeAuthenticatedRequest,
    @Res() res: Response
  ): Promise<void> {
    const abortController = new AbortController();
    req.once("close", () => {
      abortController.abort();
    });

    res.setHeader("Content-Type", "text/event-stream");
    res.setHeader("Cache-Control", "no-cache");
    res.setHeader("Connection", "keep-alive");
    res.setHeader("X-Accel-Buffering", "no");

    try {
      const upstreamResponse = await this.chatProxyService.proxyGuestChat(
        body.messages,
        abortController.signal
      );

      const upstreamContentType = upstreamResponse.headers.get("content-type");
      if (upstreamContentType) {
        res.setHeader("Content-Type", upstreamContentType);
      }

      if (!upstreamResponse.body) {
        throw new Error("LLM response did not include a stream body");
      }

      for await (const chunk of upstreamResponse.body as unknown as AsyncIterable<Uint8Array>) {
        if (res.writableEnded || res.destroyed) {
          break;
        }

        res.write(Buffer.from(chunk));
      }
    } catch (error: unknown) {
      this.logStreamError(error);

      if (!res.writableEnded && !res.destroyed) {
        res.write("event: error\n");
        res.write(`data: ${JSON.stringify({ error: this.toSafeClientMessage(error) })}\n\n`);
      }
    } finally {
      if (!res.writableEnded && !res.destroyed) {
        res.end();
      }
    }
  }

  // POST /api/chat/stream
  // Request: { messages: Array<{role, content}> }
  // Response: SSE stream of chat completion events
  // Requires authentication - uses user's personal OpenClaw container
  @Post("stream")
  @UseGuards(AuthGuard)
  async streamChat(
    @Body() body: ChatStreamDto,
    @Req() req: MaybeAuthenticatedRequest,
    @Res() res: Response
  ): Promise<void> {
    const userId = req.user?.id;
    if (!userId) {
      this.logger.warn("streamChat called without user ID after AuthGuard");
      throw new HttpException("Authentication required", 401);
    }

    const abortController = new AbortController();
    req.once("close", () => {
      abortController.abort();
    });

    res.setHeader("Content-Type", "text/event-stream");
    res.setHeader("Cache-Control", "no-cache");
    res.setHeader("Connection", "keep-alive");
    res.setHeader("X-Accel-Buffering", "no");

    try {
      const upstreamResponse = await this.chatProxyService.proxyChat(
        userId,
        body.messages,
        abortController.signal,
        body.agent
      );

      const upstreamContentType = upstreamResponse.headers.get("content-type");
      if (upstreamContentType) {
        res.setHeader("Content-Type", upstreamContentType);
      }

      if (!upstreamResponse.body) {
        throw new Error("OpenClaw response did not include a stream body");
      }

      for await (const chunk of upstreamResponse.body as unknown as AsyncIterable<Uint8Array>) {
        if (res.writableEnded || res.destroyed) {
          break;
        }

        res.write(Buffer.from(chunk));
      }
    } catch (error: unknown) {
      this.logStreamError(error);

      if (!res.writableEnded && !res.destroyed) {
        res.write("event: error\n");
        res.write(`data: ${JSON.stringify({ error: this.toSafeClientMessage(error) })}\n\n`);
      }
    } finally {
      if (!res.writableEnded && !res.destroyed) {
        res.end();
      }
    }
  }

  private toSafeClientMessage(error: unknown): string {
    if (error instanceof HttpException && error.getStatus() < 500) {
      return "Chat request was rejected";
    }

    return "Chat stream failed";
  }

  private logStreamError(error: unknown): void {
    if (error instanceof Error) {
      this.logger.warn(`Chat stream failed: ${error.message}`);
      return;
    }

    this.logger.warn(`Chat stream failed: ${String(error)}`);
  }
}
