import { Suspense } from "react"
import Link from "next/link"
import { Search, Clock } from "lucide-react"
import { getTraceData, type TraceData } from "@/app/observability/lib/get-trace-data"
import { getRecentTraces, type RecentTrace } from "@/app/observability/lib/get-recent-traces"
import { TraceSearch } from "@/app/observability/components/trace-search"
import { TimelinePageView } from "@/app/observability/components/timeline-page-view"
import { transformToTimelineSections } from "@/app/observability/components/timeline-types"
import { ErrorsSection } from "@/app/observability/components/errors-section"
import { FunctionToOptimizeSection } from "@/app/observability/components/function-to-optimize-section"
import { CodeContextSection } from "@/app/observability/components/code-context-section"

interface Observability2PageProps {
  searchParams: Promise<{
    trace_id?: string
  }>
}

export default async function Observability2Page({ searchParams }: Observability2PageProps) {
  return (
    <div className="obs-v2 min-h-screen bg-zinc-50 dark:bg-zinc-900">
      <Suspense fallback={<PageSkeleton />}>
        <ObservabilityPageContent searchParams={searchParams} />
      </Suspense>
    </div>
  )
}

async function ObservabilityPageContent({ searchParams }: Observability2PageProps) {
  const params = await searchParams
  const traceId = params.trace_id?.trim()

  const [recentTraces, traceData] = await Promise.all([
    getRecentTraces(),
    traceId ? getTraceData(traceId.substring(0, 33)) : Promise.resolve(null),
  ])

  const hasResults = traceData
    ? traceData.rawLlmCalls.length > 0 || traceData.errors.length > 0
    : false

  return (
    <>
      <div className="border-b border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800">
        <div className="max-w-4xl mx-auto px-4 py-4">
          <Suspense fallback={<SearchSkeleton />}>
            <TraceSearch initialTraceId={traceId || ""} hasResults={hasResults} />
          </Suspense>
        </div>
      </div>

      {traceId && traceData ? (
        <Suspense fallback={<TraceContentSkeleton />}>
          <TraceContent traceId={traceId} traceData={traceData} />
        </Suspense>
      ) : traceId ? (
        <TraceContentSkeleton />
      ) : (
        <EmptyState recentTraces={recentTraces} />
      )}
    </>
  )
}

function PageSkeleton() {
  return (
    <>
      <div className="border-b border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800">
        <div className="max-w-4xl mx-auto px-4 py-4">
          <SearchSkeleton />
        </div>
      </div>
      <TraceContentSkeleton />
    </>
  )
}

function buildCandidateRankMap(
  rankingData: { ranking?: string[]; explanation?: string } | null,
): Record<string, number> {
  const rankMap: Record<string, number> = {}
  if (rankingData?.ranking) {
    rankingData.ranking.forEach((id, index) => {
      rankMap[id] = index + 1
    })
  }
  return rankMap
}

function getFunctionName(
  metadata: Record<string, unknown> | undefined,
  optimizationEvent: TraceData["optimizationEvent"],
): string | null {
  const metadataFunctionName = metadata?.function_to_optimize as string | undefined
  return metadataFunctionName ?? optimizationEvent?.function_name ?? null
}

function getFilePath(
  optimizationEvent: TraceData["optimizationEvent"],
  metadata: Record<string, unknown> | undefined,
): string | null {
  const eventFilePath = optimizationEvent?.file_path
  const metadataFilePath = metadata?.file_path as string | undefined
  return eventFilePath ?? metadataFilePath ?? null
}

function TraceContent({ traceId, traceData }: { traceId: string; traceData: TraceData }) {
  const { rawLlmCalls, errors, optimizationFeatures, optimizationEvent } = traceData

  if (rawLlmCalls.length === 0 && errors.length === 0) {
    return <NotFoundState traceId={traceId} />
  }

  const optimizationsOrigin =
    (optimizationFeatures?.optimizations_origin as Record<
      string,
      { source: string; model?: string; call_sequence?: number; parent?: string }
    >) || {}

  const candidateExplanations =
    (optimizationFeatures?.explanations_post as Record<string, string>) || {}

  const allCandidates = optimizationFeatures?.optimizations_post
    ? Object.entries(optimizationFeatures.optimizations_post as Record<string, string>).map(
        ([id, code]) => ({
          id,
          code: typeof code === "string" ? code : "",
          source: optimizationsOrigin[id]?.source || "OPTIMIZE",
          model: optimizationsOrigin[id]?.model,
          callSequence: optimizationsOrigin[id]?.call_sequence,
          explanation: candidateExplanations[id],
        }),
      )
    : []

  const optimizationCandidates = allCandidates
    .filter(c => c.source === "OPTIMIZE")
    .sort((a, b) => (a.callSequence ?? Infinity) - (b.callSequence ?? Infinity))
    .map((c, index) => ({ ...c, index: index + 1 }))

  const lineProfilerCandidates = allCandidates
    .filter(c => c.source === "OPTIMIZE_LP")
    .sort((a, b) => (a.callSequence ?? Infinity) - (b.callSequence ?? Infinity))
    .map((c, index) => ({ ...c, index: index + 1 }))

  const refinementCandidates = allCandidates
    .filter(c => c.source === "REFINE")
    .sort((a, b) => (a.callSequence ?? Infinity) - (b.callSequence ?? Infinity))
    .map((c, index) => ({
      ...c,
      index: index + 1,
      parentId: optimizationsOrigin[c.id]?.parent || null,
    }))

  const adaptiveCandidates = allCandidates
    .filter(c => c.source === "ADAPTIVE")
    .sort((a, b) => (a.callSequence ?? Infinity) - (b.callSequence ?? Infinity))
    .map((c, index) => ({
      ...c,
      index: index + 1,
      parentId: optimizationsOrigin[c.id]?.parent || null,
    }))

  const rankingData = optimizationFeatures?.ranking as {
    ranking?: string[]
    explanation?: string
  } | null
  const bestCandidateId = rankingData?.ranking?.[0] ?? null

  const pullRequestRaw = optimizationFeatures?.pull_request
  const usedForPr = Boolean(
    pullRequestRaw != null &&
    typeof pullRequestRaw === "object" &&
    !Array.isArray(pullRequestRaw) &&
    Object.keys(pullRequestRaw as Record<string, unknown>).length > 0,
  )

  const candidateRankMap = buildCandidateRankMap(rankingData)

  const generatedTests = ((optimizationFeatures?.generated_test ?? []) as string[]).map(
    (code: string, index: number) => ({
      code,
      index: index + 1,
    }),
  )

  const instrumentedTests = (
    (optimizationFeatures?.instrumented_generated_test ?? []) as string[]
  ).map((code: string, index: number) => ({
    code,
    index: index + 1,
  }))

  const instrumentedPerfTests = (
    (optimizationFeatures?.instrumented_perf_test ?? []) as string[]
  ).map((code: string, index: number) => ({
    code,
    index: index + 1,
  }))

  type RawLlmCall = {
    id: string
    trace_id: string | null
    call_type: string | null
    model_name: string | null
    status: string
    latency_ms: number | null
    llm_cost: number | null
    total_tokens: number | null
    created_at: Date
    context: unknown
  }

  const llmCalls = (rawLlmCalls as RawLlmCall[]).sort((a: RawLlmCall, b: RawLlmCall) => {
    const seqA = (a.context as { call_sequence?: number } | null)?.call_sequence ?? Infinity
    const seqB = (b.context as { call_sequence?: number } | null)?.call_sequence ?? Infinity
    if (seqA !== seqB) return seqA - seqB
    return new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
  })

  const transformedCalls = llmCalls.map((call: RawLlmCall) => ({
    id: call.id,
    call_type: call.call_type,
    model_name: call.model_name,
    status: call.status,
    latency_ms: call.latency_ms,
    llm_cost: call.llm_cost,
    total_tokens: call.total_tokens,
    created_at: call.created_at,
    context: call.context as { call_sequence?: number } | null,
  }))

  const { sections, totalDuration } = transformToTimelineSections({
    calls: transformedCalls,
    optimizationCandidates,
    lineProfilerCandidates,
    refinementCandidates,
    adaptiveCandidates,
    generatedTests,
    instrumentedTests,
    instrumentedPerfTests,
    originalCode: optimizationFeatures?.original_code ?? null,
    testFramework: optimizationFeatures?.test_framework ?? null,
    candidateRankMap,
    bestCandidateId,
    rankingExplanation: rankingData?.explanation ?? null,
    usedForPr,
  })

  const transformedErrors = errors.map(
    (error: {
      id: string
      error_type: string | null
      severity: string | null
      error_message: string | null
      context: unknown
      created_at: Date
    }) => ({
      id: error.id,
      error_type: error.error_type ?? "unknown",
      severity: error.severity ?? "unknown",
      error_message: error.error_message ?? "",
      context: error.context as {
        test_name?: string
        failure_reason?: string
        test_output?: string
        expected?: string
        actual?: string
      } | null,
      created_at: error.created_at,
    }),
  )

  const featuresMetadata = optimizationFeatures?.metadata as Record<string, unknown> | undefined
  const functionName = getFunctionName(featuresMetadata, optimizationEvent)
  const filePath = getFilePath(optimizationEvent, featuresMetadata)
  const originalCode = optimizationFeatures?.original_code ?? null
  const dependencyCode = optimizationFeatures?.dependency_code ?? null

  return (
    <div>
      <div className="max-w-4xl mx-auto px-4 py-6 space-y-4">
        <FunctionToOptimizeSection
          functionName={functionName}
          filePath={filePath}
          originalCode={originalCode}
        />
        <CodeContextSection
          functionName={functionName}
          filePath={filePath}
          originalCode={originalCode}
          dependencyCode={dependencyCode}
        />
      </div>

      <TimelinePageView
        sections={sections}
        totalDuration={totalDuration}
        functionName={functionName}
        filePath={filePath}
        traceId={traceId}
      />

      {transformedErrors.length > 0 && (
        <div className="max-w-4xl mx-auto px-4 pb-20">
          <ErrorsSection errors={transformedErrors} />
        </div>
      )}
    </div>
  )
}

function formatRelativeTime(date: Date | string): string {
  const now = Date.now()
  const diffMs = now - new Date(date).getTime()
  const diffSec = Math.floor(diffMs / 1000)
  if (diffSec < 60) return "just now"
  const diffMin = Math.floor(diffSec / 60)
  if (diffMin < 60) return `${diffMin}m ago`
  const diffHr = Math.floor(diffMin / 60)
  if (diffHr < 24) return `${diffHr}h ago`
  const diffDay = Math.floor(diffHr / 24)
  return `${diffDay}d ago`
}

function EmptyState({ recentTraces }: { recentTraces: RecentTrace[] }) {
  return (
    <div className="flex flex-col items-center justify-center py-16 text-center px-4">
      <div className="w-20 h-20 mb-8 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center">
        <Search className="h-10 w-10 text-zinc-400" />
      </div>
      <h3 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-50 mb-3">
        Enter a Trace ID to Get Started
      </h3>
      <p className="text-zinc-500 dark:text-zinc-400 max-w-md leading-relaxed">
        Paste or type a trace ID in the search box above to view the complete optimization timeline,
        including all LLM calls, generated candidates, and any errors.
      </p>

      {recentTraces.length > 0 && (
        <div className="mt-10 w-full max-w-md">
          <div className="flex items-center gap-2 mb-4 justify-center">
            <Clock className="h-4 w-4 text-zinc-400" />
            <h4 className="text-sm font-medium text-zinc-500 dark:text-zinc-400">Recent Traces</h4>
          </div>
          <ul className="space-y-2">
            {recentTraces.map(trace => (
              <li key={trace.traceId}>
                <Link
                  href={`/observability?trace_id=${trace.traceId}`}
                  className="flex items-center justify-between px-4 py-3 rounded-lg bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 hover:border-zinc-300 dark:hover:border-zinc-600 hover:shadow-sm transition-all"
                >
                  <div className="flex flex-col items-start gap-0.5 min-w-0">
                    <code className="text-sm font-mono text-zinc-900 dark:text-zinc-100 truncate max-w-full">
                      {trace.traceId.substring(0, 12)}...
                    </code>
                    {trace.functionName && (
                      <span className="text-xs text-zinc-500 dark:text-zinc-400 truncate max-w-full">
                        {trace.functionName}
                        {trace.filePath && (
                          <span className="text-zinc-400 dark:text-zinc-500">
                            {" "}
                            in {trace.filePath.split("/").pop()}
                          </span>
                        )}
                      </span>
                    )}
                  </div>
                  <span className="text-xs text-zinc-400 dark:text-zinc-500 whitespace-nowrap ml-4">
                    {formatRelativeTime(trace.lastSeenAt)}
                  </span>
                </Link>
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  )
}

function NotFoundState({ traceId }: { traceId: string }) {
  return (
    <div className="flex flex-col items-center justify-center py-32 text-center px-4">
      <div className="w-20 h-20 mb-8 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
        <Search className="h-10 w-10 text-red-500" />
      </div>
      <h3 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-50 mb-3">
        Trace Not Found
      </h3>
      <p className="text-zinc-500 dark:text-zinc-400 max-w-md mb-6">
        No data was found for the trace ID:
      </p>
      <code className="text-sm font-mono text-zinc-900 dark:text-zinc-50 bg-zinc-100 dark:bg-zinc-700 px-4 py-2 rounded-md">
        {traceId}
      </code>
      <p className="text-zinc-500 dark:text-zinc-400 max-w-md mt-6 text-sm">
        Please check that the trace ID is correct and try again.
      </p>
    </div>
  )
}

function SearchSkeleton() {
  return (
    <div className="w-full max-w-2xl mx-auto">
      <div className="flex items-center gap-3">
        <div className="flex-1 h-12 bg-zinc-200 dark:bg-zinc-700 rounded-md animate-pulse" />
        <div className="w-24 h-12 bg-zinc-200 dark:bg-zinc-700 rounded-md animate-pulse" />
      </div>
    </div>
  )
}

function TraceContentSkeleton() {
  return (
    <div className="max-w-4xl mx-auto px-4 py-8">
      <div className="mb-8">
        <div className="h-4 w-48 bg-zinc-200 dark:bg-zinc-700 rounded mb-2 animate-pulse" />
        <div className="h-1 w-full bg-zinc-200 dark:bg-zinc-700 rounded animate-pulse" />
      </div>

      <div className="space-y-8">
        {[1, 2, 3].map(i => (
          <div key={i} className="flex gap-4">
            <div className="w-5 h-5 rounded-full bg-zinc-200 dark:bg-zinc-700 animate-pulse" />
            <div className="flex-1">
              <div className="h-6 w-32 bg-zinc-200 dark:bg-zinc-700 rounded mb-3 animate-pulse" />
              <div className="h-48 bg-zinc-200 dark:bg-zinc-700 rounded animate-pulse" />
            </div>
          </div>
        ))}
      </div>
    </div>
  )
}
