/**
 * Voice-to-Job Endpoint
 *
 * Consolidated endpoint for voice-based job posting:
 * 1. Whisper transcribes audio
 * 2. GPT-4o-mini extracts structured job fields from transcript
 *
 * Returns all fields needed to auto-fill the post-job form.
 */

import { NextRequest, NextResponse } from "next/server";
import { logger } from "@/lib/logger";
import OpenAI from "openai";
import { createClient } from "@/lib/supabase/server";
import { checkRateLimit, getClientIP, createRateLimitResponse } from "@/lib/rate-limit";
import { sanitizeInput, stripContactInfo } from "@/lib/sanitize";
import { withCSRFProtection } from "@/lib/csrf";
import {
  AI_RATE_LIMIT,
  AI_WINDOW_MS,
  MAX_LENGTH_TRANSCRIPT,
} from "@/lib/constants";

if (!process.env.OPENAI_API_KEY) {
  throw new Error("OPENAI_API_KEY environment variable is not set");
}

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

const VALID_TRADES = [
  "steel-fixer",
  "formworker",
  "labourer",
  "concreter",
  "carpenter",
  "scaffolder",
  "other",
];

const handler = async (request: NextRequest) => {
  try {
    const clientIP = getClientIP(request);
    const rateLimit = await checkRateLimit(`ai-voice-job:${clientIP}`, AI_RATE_LIMIT, AI_WINDOW_MS);

    if (!rateLimit.allowed) {
      const retryAfter = Math.ceil((rateLimit.resetTime - Date.now()) / 1000);
      return createRateLimitResponse(clientIP, retryAfter);
    }

    const supabase = await createClient();
    const { data: { user }, error: authError } = await supabase.auth.getUser();
    if (authError || !user) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    // Parse multipart form data with audio file
    const contentType = request.headers.get("content-type") || "";

    let audioFile: File | null = null;
    let existingTranscript: string | null = null;

    if (contentType.includes("multipart/form-data")) {
      const formData = await request.formData();
      audioFile = formData.get("audio") as File | null;
      existingTranscript = formData.get("transcript") as string | null;
    } else {
      const body = await request.json();
      existingTranscript = body.transcript;
    }

    if (!audioFile && !existingTranscript) {
      return NextResponse.json(
        { error: "No audio file or transcript provided" },
        { status: 400 }
      );
    }

    // Step 1: Transcribe audio
    let transcript: string;

    if (audioFile) {
      try {
        const transcription = await openai.audio.transcriptions.create({
          file: audioFile,
          model: "whisper-1",
          language: "en",
        });
        transcript = transcription.text;
      } catch (error) {
        logger.error("Voice-to-job transcription error:", error);
        return NextResponse.json(
          { error: "Failed to transcribe audio" },
          { status: 500 }
        );
      }
    } else {
      transcript = existingTranscript!;
    }

    if (!transcript.trim()) {
      return NextResponse.json(
        { error: "No speech detected in audio" },
        { status: 400 }
      );
    }

    const sanitizedTranscript = sanitizeInput(transcript, { maxLength: MAX_LENGTH_TRANSCRIPT });

    // Step 2: Extract structured job data from transcript
    try {
      const completion = await openai.chat.completions.create({
        model: "gpt-4o-mini",
        messages: [
          {
            role: "system",
            content: `You are an Australian construction industry expert helping contractors post jobs quickly from voice input.

Extract structured job posting data from the contractor's spoken description. Use Australian English.

Valid trade IDs: ${VALID_TRADES.join(", ")}

Trade matching guide:
- "steelfixer" / "steel fixer" / "reo" / "rebar" → "steel-fixer"
- "formworker" / "formwork" / "shuttering" / "shutter hand" → "formworker"
- "labourer" / "laborer" / "general hand" / "offsider" → "labourer"
- "concreter" / "concrete" / "slab" → "concreter"
- "carpenter" / "chippie" / "chippy" → "carpenter"
- "scaffolder" / "scaffold" → "scaffolder"
- Anything else → "other"

Return ONLY valid JSON in this exact format:
{
  "trade": "one of the valid trade IDs",
  "title": "concise job title (e.g. 'Steel Fixers Needed for High-Rise')",
  "description": "3-4 sentence professional job description incorporating details from the voice input. Mention key duties, site expectations, PPE requirements. Use Australian English.",
  "certifications": ["array of required certs — always include 'White Card', add others as appropriate"],
  "rateMin": number or null (minimum hourly/daily rate in AUD if mentioned),
  "rateMax": number or null (maximum hourly/daily rate in AUD if mentioned),
  "rateType": "hourly" or "daily" (default "hourly"),
  "suburb": "suburb or area name if mentioned, or null",
  "siteAddress": "full site address if mentioned, or null",
  "startDate": "ISO date string (YYYY-MM-DD) if a start date is mentioned, or null. Convert relative dates like 'next Monday' based on today being ${new Date().toISOString().split("T")[0]}.",
  "duration": "duration string if mentioned (e.g. '2 weeks', 'ongoing'), or null",
  "workersNeeded": number (default 1, extract from voice if mentioned)
}

Important:
- If a rate like "45 bucks an hour" is mentioned, set rateMin to that value and rateMax to rateMin + 10.
- If a daily rate is mentioned (e.g. "$400 a day"), set rateType to "daily".
- Always generate a professional title and description even if the voice input is casual.
- Capture ALL details mentioned — location, timing, number of workers, rates, specific requirements.`,
          },
          {
            role: "user",
            content: sanitizedTranscript,
          },
        ],
        temperature: 0.3,
        response_format: { type: "json_object" },
      });

      const content = completion.choices[0]?.message?.content;
      if (!content) {
        throw new Error("No response from AI");
      }

      const generated = JSON.parse(content);

      // Validate trade
      const trade = VALID_TRADES.includes(generated.trade) ? generated.trade : "other";

      // Validate and clamp rates
      const MIN_RATE = 25;
      const MAX_RATE = 500;
      let rateMin = typeof generated.rateMin === "number" ? generated.rateMin : null;
      let rateMax = typeof generated.rateMax === "number" ? generated.rateMax : null;

      if (rateMin !== null) {
        rateMin = Math.max(MIN_RATE, Math.min(rateMin, MAX_RATE));
      }
      if (rateMax !== null) {
        rateMax = Math.max(MIN_RATE, Math.min(rateMax, MAX_RATE));
      }
      if (rateMin !== null && rateMax !== null && rateMax < rateMin) {
        [rateMin, rateMax] = [rateMax, rateMin];
      }

      // Validate workersNeeded
      const workersNeeded = typeof generated.workersNeeded === "number" && generated.workersNeeded >= 1
        ? Math.min(Math.round(generated.workersNeeded), 50)
        : 1;

      return NextResponse.json({
        transcript: sanitizedTranscript,
        trade,
        title: stripContactInfo(generated.title || ""),
        description: stripContactInfo(generated.description || ""),
        certifications: Array.isArray(generated.certifications) ? generated.certifications : ["White Card"],
        rateMin,
        rateMax,
        rateType: generated.rateType === "daily" ? "daily" : "hourly",
        suburb: generated.suburb || null,
        siteAddress: generated.siteAddress || null,
        startDate: generated.startDate || null,
        duration: generated.duration || null,
        workersNeeded,
      });
    } catch (error) {
      logger.error("Voice-to-job AI processing error:", error);

      // Return transcript so user can still use manual entry
      return NextResponse.json({
        transcript: sanitizedTranscript,
        error: "AI processing failed. Please fill in the details manually.",
        fallback: true,
      });
    }
  } catch (error) {
    logger.error("Voice-to-job error:", error);

    if (error instanceof Error) {
      if (error.message.includes("API key")) {
        return NextResponse.json({ error: "AI service configuration error" }, { status: 503 });
      }
      if (error.message.includes("rate limit") || error.message.includes("429")) {
        return NextResponse.json({ error: "AI service is busy. Please try again." }, { status: 429 });
      }
    }

    return NextResponse.json(
      { error: "Failed to process voice input. Please try again." },
      { status: 500 }
    );
  }
};

export const POST = withCSRFProtection(handler);
