/*!
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";

import { Wrapper } from "src/utils/Wrapper";

import { renderStructuredLog, renderTIContextPreamble, tiContextFields } from "./renderStructuredLog";

const translate = (key: string) => key;

describe("tiContextFields", () => {
  it("contains the six fields bound via bind_contextvars", () => {
    expect(tiContextFields).toEqual(
      expect.arrayContaining(["ti_id", "dag_id", "task_id", "run_id", "try_number", "map_index"]),
    );
    expect(tiContextFields).toHaveLength(6);
  });
});

describe("renderStructuredLog — traceback frame highlighting", () => {
  const sitepkgFile = "/usr/local/lib/python3.14/site-packages/airflow/sdk/bases/operator.py";
  const dagBundleFile = "/tmp/airflow/dag_bundles/astro/main/dags/non_alert/enrich_ticket.py";

  const renderWithError = () => {
    const result = renderStructuredLog({
      index: 0,
      logLink: "",
      logMessage: {
        error_detail: [
          {
            exc_notes: [],
            exc_type: "TypeError",
            exc_value: "fromisoformat: argument must be str",
            frames: [
              { filename: sitepkgFile, lineno: 445, name: "wrapper" },
              { filename: dagBundleFile, lineno: 226, name: "retrieve_cluster" },
            ],
            is_cause: false,
            syntax_error: null,
          },
        ],
        event: "Task failed with exception",
        level: "error",
        timestamp: "2026-07-22T09:15:20Z",
      },
      renderingMode: "jsx",
      translate: translate as never,
    });

    return render(<Wrapper>{result}</Wrapper>);
  };

  it("marks a DAG-bundle frame as user code and a site-packages frame as library", () => {
    renderWithError();

    expect(screen.getByText(JSON.stringify(dagBundleFile))).toHaveAttribute("data-frame-source", "user");
    expect(screen.getByText(JSON.stringify(sitepkgFile))).toHaveAttribute("data-frame-source", "library");
  });
});

describe("renderStructuredLog — TI context field stripping", () => {
  it("does not render TI context fields as per-line structured attributes", () => {
    const result = renderStructuredLog({
      index: 0,
      logLink: "",
      logMessage: {
        dag_id: "my_dag",
        event: "Task started",
        level: "info",
        map_index: -1,
        run_id: "run_1",
        task_id: "my_task",
        ti_id: "abc-123",
        timestamp: "2025-01-01T00:00:00Z",
        try_number: 1,
      },
      renderingMode: "jsx",
      translate: translate as never,
    });

    render(<Wrapper>{result}</Wrapper>);

    for (const field of tiContextFields) {
      expect(screen.queryByText(new RegExp(`${field}=`, "u"))).toBeNull();
    }
    expect(screen.getByText("Task started")).toBeInTheDocument();
  });

  it("still renders non-TI structured fields normally", () => {
    const result = renderStructuredLog({
      index: 0,
      logLink: "",
      logMessage: {
        dag_id: "my_dag",
        event: "Task started",
        level: "info",
        some_custom_key: "some_value",
        ti_id: "abc-123",
        timestamp: "2025-01-01T00:00:00Z",
      },
      renderingMode: "jsx",
      translate: translate as never,
    });

    render(<Wrapper>{result}</Wrapper>);

    expect(screen.getByText(/some_custom_key/u)).toBeInTheDocument();
    expect(screen.queryByText(/ti_id/u)).toBeNull();
  });
});

describe("renderTIContextPreamble", () => {
  it("text mode: returns key=value pairs joined by spaces, prefixed with label", () => {
    const result = renderTIContextPreamble(
      { dag_id: "my_dag", task_id: "my_task", ti_id: "abc-123" },
      "text",
      "Task Identity",
    );

    expect(result).toContain("Task Identity");
    expect(result).toContain("ti_id=abc-123");
    expect(result).toContain("dag_id=my_dag");
    expect(result).toContain("task_id=my_task");
  });

  it("text mode: no label when omitted", () => {
    const result = renderTIContextPreamble({ dag_id: "my_dag", ti_id: "abc-123" }, "text");

    expect(result).toContain("dag_id=my_dag");
    expect(result).toContain("ti_id=abc-123");
    expect(result).not.toContain("Task Identity");
  });

  it("text mode: only renders fields present in context", () => {
    const result = renderTIContextPreamble({ ti_id: "abc-123" }, "text", "Task Identity");

    expect(result).toContain("ti_id=abc-123");
    expect(result).not.toContain("dag_id");
  });

  it("jsx mode: renders label and key=value spans", () => {
    const element = renderTIContextPreamble(
      { dag_id: "my_dag", ti_id: "abc-123", try_number: 1 },
      "jsx",
      "Task Identity",
    );

    const { container } = render(<Wrapper>{element}</Wrapper>);

    expect(screen.getByText("Task Identity")).toBeInTheDocument();
    // Keys render in their own spans
    expect(screen.getByText("dag_id")).toBeInTheDocument();
    expect(screen.getByText("ti_id")).toBeInTheDocument();
    // Values are text nodes adjacent to the = sign; check via container text content
    expect(container.textContent).toContain("dag_id=my_dag");
    expect(container.textContent).toContain("ti_id=abc-123");
  });

  it("jsx mode: no label element when label is omitted", () => {
    const element = renderTIContextPreamble({ dag_id: "my_dag" }, "jsx");

    render(<Wrapper>{element}</Wrapper>);

    expect(screen.queryByText("Task Identity")).toBeNull();
    expect(screen.getByText("dag_id")).toBeInTheDocument();
  });

  it("jsx mode: only renders fields present in context", () => {
    const element = renderTIContextPreamble({ ti_id: "abc-123" }, "jsx", "Task Identity");

    render(<Wrapper>{element}</Wrapper>);

    expect(screen.getByText("ti_id")).toBeInTheDocument();
    expect(screen.queryByText("dag_id")).toBeNull();
  });

  it("jsx mode: empty context renders label only", () => {
    const element = renderTIContextPreamble({}, "jsx", "Task Identity");

    render(<Wrapper>{element}</Wrapper>);

    expect(screen.getByText("Task Identity")).toBeInTheDocument();
  });
});
