/**
 * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026)
 *
 * Licensed 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 { screen } from "@testing-library/react"
import { userEvent } from "@testing-library/user-event"

import { render, renderWithContexts } from "@streamlit/lib/testing"
import { Logo as LogoProto } from "@streamlit/protobuf"

import LogoComponent from "./LogoComponent"

// Mock StreamlitConfig using global mock state (see vitest.setup.ts)
vi.mock("@streamlit/utils", async () => {
  const actual = await vi.importActual("@streamlit/utils")
  return {
    ...actual,
    get StreamlitConfig() {
      return globalThis.__mockStreamlitConfig
    },
  }
})

const mockEndpoints = {
  setStaticConfigUrl: vi.fn(),
  sendClientErrorToHost: vi.fn(),
  checkSourceUrlResponse: vi.fn(),
  buildBidiComponentURL: vi.fn(
    (componentName: string, path: string) => `${componentName}/${path}`
  ),
  buildComponentURL: vi.fn(
    (componentName: string, path: string) => `${componentName}/${path}`
  ),
  buildMediaURL: vi.fn((url: string) => url),
  buildDownloadUrl: vi.fn((url: string) => url),
  buildAppPageURL: vi.fn((_baseUrl, page) => page.pageName || ""),
  uploadFileUploaderFile: vi.fn(),
}

interface TestProps {
  appLogo: LogoProto | null
  collapsed: boolean
  endpoints: typeof mockEndpoints
  dataTestId?: string
}

const getProps = (props: Partial<TestProps> = {}): TestProps => ({
  appLogo: null,
  collapsed: false,
  endpoints: mockEndpoints,
  ...props,
})

describe("LogoComponent", () => {
  const sampleLogo = LogoProto.create({
    image: "https://example.com/logo.png",
    iconImage: "https://example.com/icon.png",
    link: "https://example.com",
    size: "medium",
  })

  beforeEach(() => {
    vi.clearAllMocks()
  })

  it("renders nothing when appLogo is null", () => {
    const { container } = render(<LogoComponent {...getProps()} />)
    expect(container.firstChild).toBeNull()
  })

  it("renders logo in header when dataTestId is stHeaderLogo", () => {
    render(
      <LogoComponent
        {...getProps({
          appLogo: sampleLogo,
          dataTestId: "stHeaderLogo",
        })}
      />
    )

    const logo = screen.getByTestId("stHeaderLogo")
    expect(logo).toHaveAttribute("src", "https://example.com/logo.png")
  })

  it("renders logo in sidebar when dataTestId is stSidebarLogo", () => {
    render(
      <LogoComponent
        {...getProps({
          appLogo: sampleLogo,
          dataTestId: "stSidebarLogo",
        })}
      />
    )

    const logo = screen.getByTestId("stSidebarLogo")
    expect(logo).toHaveAttribute("src", "https://example.com/logo.png")
  })

  it("uses iconImage when collapsed is true and iconImage exists", () => {
    render(
      <LogoComponent
        {...getProps({
          appLogo: sampleLogo,
          dataTestId: "stHeaderLogo",
          collapsed: true,
        })}
      />
    )

    const logo = screen.getByTestId("stHeaderLogo")
    expect(logo).toHaveAttribute("src", "https://example.com/icon.png")
    expect(mockEndpoints.buildMediaURL).toHaveBeenCalledWith(
      "https://example.com/icon.png"
    )
  })

  it("falls back to main image when collapsed but no iconImage", () => {
    const logoWithoutIcon = LogoProto.create({
      image: "https://example.com/logo.png",
      size: "medium",
    })

    render(
      <LogoComponent
        {...getProps({
          appLogo: logoWithoutIcon,
          dataTestId: "stHeaderLogo",
          collapsed: true,
        })}
      />
    )

    const logo = screen.getByTestId("stHeaderLogo")
    expect(logo).toHaveAttribute("src", "https://example.com/logo.png")
  })

  it("renders logo with link when link is provided", () => {
    render(
      <LogoComponent
        {...getProps({
          appLogo: sampleLogo,
          dataTestId: "stHeaderLogo",
        })}
      />
    )

    const logoLink = screen.getByTestId("stLogoLink")
    expect(logoLink).toHaveAttribute("href", "https://example.com")
  })

  it("renders logo without link when no link provided and single-page app", () => {
    const logoWithoutLink = LogoProto.create({
      image: "https://example.com/logo.png",
      size: "medium",
    })

    render(
      <LogoComponent
        {...getProps({
          appLogo: logoWithoutLink,
          dataTestId: "stHeaderLogo",
        })}
      />
    )

    expect(screen.queryByTestId("stLogoLink")).not.toBeInTheDocument()
    screen.getByTestId("stHeaderLogo")
  })

  describe("multi-page app home navigation", () => {
    const logoWithoutLink = LogoProto.create({
      image: "https://example.com/logo.png",
      size: "medium",
    })

    const multiPageAppPages = [
      { pageName: "Home", pageScriptHash: "home_hash", isDefault: true },
      { pageName: "Page 2", pageScriptHash: "page2_hash", isDefault: false },
    ]

    it("renders clickable button when no link provided in multi-page app", () => {
      renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: logoWithoutLink,
            dataTestId: "stHeaderLogo",
          })}
        />,
        {
          navigationContext: {
            appPages: multiPageAppPages,
          },
        }
      )

      const logoButton = screen.getByTestId("stLogoLink")
      expect(logoButton.tagName).toBe("BUTTON")
      expect(logoButton).toHaveAttribute("aria-label", "Navigate to home page")
    })

    it("calls onPageChange with home page hash when clicked", async () => {
      const user = userEvent.setup()
      const mockOnPageChange = vi.fn()

      renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: logoWithoutLink,
            dataTestId: "stHeaderLogo",
          })}
        />,
        {
          navigationContext: {
            appPages: multiPageAppPages,
            onPageChange: mockOnPageChange,
          },
        }
      )

      const logoButton = screen.getByTestId("stLogoLink")
      await user.click(logoButton)

      expect(mockOnPageChange).toHaveBeenCalledWith("home_hash")
    })

    it("renders non-clickable logo when already on home page", () => {
      renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: logoWithoutLink,
            dataTestId: "stHeaderLogo",
          })}
        />,
        {
          navigationContext: {
            appPages: multiPageAppPages,
            currentPageScriptHash: "home_hash", // Already on home page
          },
        }
      )

      // Should not render a clickable button when already on home page
      expect(screen.queryByTestId("stLogoLink")).not.toBeInTheDocument()
      // Logo should still be rendered
      screen.getByTestId("stHeaderLogo")
    })

    it("uses external link when link is provided, even in multi-page app", () => {
      renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: sampleLogo, // Has link: "https://example.com"
            dataTestId: "stHeaderLogo",
          })}
        />,
        {
          navigationContext: {
            appPages: multiPageAppPages,
          },
        }
      )

      const logoLink = screen.getByTestId("stLogoLink")
      expect(logoLink.tagName).toBe("A")
      expect(logoLink).toHaveAttribute("href", "https://example.com")
      expect(logoLink).toHaveAttribute("target", "_blank")
    })

    it("renders non-clickable logo when single-page app with no link", () => {
      const singlePageAppPages = [
        { pageName: "Home", pageScriptHash: "home_hash", isDefault: true },
      ]

      renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: logoWithoutLink,
            dataTestId: "stHeaderLogo",
          })}
        />,
        {
          navigationContext: {
            appPages: singlePageAppPages,
          },
        }
      )

      expect(screen.queryByTestId("stLogoLink")).not.toBeInTheDocument()
      screen.getByTestId("stHeaderLogo")
    })

    it("renders non-clickable logo when no pages are available", () => {
      renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: logoWithoutLink,
            dataTestId: "stHeaderLogo",
          })}
        />,
        {
          navigationContext: {
            appPages: [],
          },
        }
      )

      expect(screen.queryByTestId("stLogoLink")).not.toBeInTheDocument()
      screen.getByTestId("stHeaderLogo")
    })
  })

  it("applies correct size classes", () => {
    const smallLogo = LogoProto.create({
      image: "https://example.com/logo.png",
      size: "small",
    })

    const { rerender } = render(
      <LogoComponent
        {...getProps({
          appLogo: smallLogo,
          dataTestId: "stHeaderLogo",
        })}
      />
    )

    let logo = screen.getByTestId("stHeaderLogo")
    expect(logo).toHaveStyle("height: 1.25rem")

    const largeLogo = LogoProto.create({
      image: "https://example.com/logo.png",
      size: "large",
    })

    rerender(
      <LogoComponent
        {...getProps({
          appLogo: largeLogo,
          dataTestId: "stHeaderLogo",
        })}
      />
    )

    logo = screen.getByTestId("stHeaderLogo")
    expect(logo).toHaveStyle("height: 2rem")
  })

  describe("crossOrigin attribute", () => {
    afterEach(() => {
      globalThis.__mockStreamlitConfig = {}
    })

    it("sets crossOrigin for relative URLs when BACKEND_BASE_URL is set", () => {
      // Setup StreamlitConfig.BACKEND_BASE_URL
      globalThis.__mockStreamlitConfig.BACKEND_BASE_URL =
        "http://localhost:8501"

      const logoWithRelativeUrl = LogoProto.create({
        image: "/media/logo.png",
        size: "medium",
      })

      renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: logoWithRelativeUrl,
            dataTestId: "stHeaderLogo",
          })}
        />,
        {
          libConfigContext: {
            resourceCrossOriginMode: "anonymous",
          },
        }
      )

      const logo = screen.getByTestId("stHeaderLogo")
      expect(logo).toHaveAttribute("crossOrigin", "anonymous")
    })

    it("does not set crossOrigin for relative URLs when BACKEND_BASE_URL is not set (same-origin)", () => {
      const logoWithRelativeUrl = LogoProto.create({
        image: "/media/logo.png",
        size: "medium",
      })

      renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: logoWithRelativeUrl,
            dataTestId: "stHeaderLogo",
          })}
        />,
        {
          libConfigContext: {
            resourceCrossOriginMode: "anonymous",
          },
        }
      )

      const logo = screen.getByTestId("stHeaderLogo")
      expect(logo).not.toHaveAttribute("crossOrigin")
    })

    it("sets crossOrigin attribute for backend URLs when configured", () => {
      globalThis.__mockStreamlitConfig.BACKEND_BASE_URL =
        "http://localhost:8501"

      const logoWithBackendUrl = LogoProto.create({
        image: "http://localhost:8501/media/logo.png",
        iconImage: "http://localhost:8501/media/icon.png",
        size: "medium",
      })

      const { rerender } = renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: logoWithBackendUrl,
            dataTestId: "stHeaderLogo",
            collapsed: false,
          })}
        />,
        {
          libConfigContext: { resourceCrossOriginMode: "anonymous" },
        }
      )

      // Test main image
      let logo = screen.getByTestId("stHeaderLogo")
      expect(logo).toHaveAttribute("crossOrigin", "anonymous")
      expect(logo).toHaveAttribute(
        "src",
        "http://localhost:8501/media/logo.png"
      )

      // Test icon image when collapsed
      rerender(
        <LogoComponent
          {...getProps({
            appLogo: logoWithBackendUrl,
            dataTestId: "stHeaderLogo",
            collapsed: true,
          })}
        />
      )

      logo = screen.getByTestId("stHeaderLogo")
      expect(logo).toHaveAttribute("crossOrigin", "anonymous")
      expect(logo).toHaveAttribute(
        "src",
        "http://localhost:8501/media/icon.png"
      )
    })

    it("does not set crossOrigin attribute for external URLs", () => {
      globalThis.__mockStreamlitConfig.BACKEND_BASE_URL =
        "http://localhost:8501"

      renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: sampleLogo, // Uses https://example.com URLs
            dataTestId: "stHeaderLogo",
          })}
        />,
        {
          libConfigContext: { resourceCrossOriginMode: "anonymous" },
        }
      )

      const logo = screen.getByTestId("stHeaderLogo")
      // External URLs should not have crossOrigin attribute set
      expect(logo).not.toHaveAttribute("crossOrigin")
      expect(logo).toHaveAttribute("src", "https://example.com/logo.png")
    })

    it.each([
      { backendBaseUrl: undefined, description: "without BACKEND_BASE_URL" },
      {
        backendBaseUrl: "http://localhost:8501",
        description: "with BACKEND_BASE_URL",
      },
    ])(
      "does not set crossOrigin attribute when resourceCrossOriginMode is undefined ($description)",
      ({ backendBaseUrl }) => {
        // Setup StreamlitConfig.BACKEND_BASE_URL if specified
        if (backendBaseUrl) {
          globalThis.__mockStreamlitConfig.BACKEND_BASE_URL = backendBaseUrl
        }

        const logoWithRelativeUrl = LogoProto.create({
          image: "/media/logo.png",
          size: "medium",
        })

        renderWithContexts(
          <LogoComponent
            {...getProps({
              appLogo: logoWithRelativeUrl,
              dataTestId: "stHeaderLogo",
            })}
          />,
          {
            libConfigContext: { resourceCrossOriginMode: undefined },
          }
        )

        const logo = screen.getByTestId("stHeaderLogo")
        expect(logo).not.toHaveAttribute("crossOrigin")
      }
    )

    it("works with use-credentials mode", () => {
      globalThis.__mockStreamlitConfig.BACKEND_BASE_URL =
        "http://localhost:8501"

      const logoWithRelativeUrl = LogoProto.create({
        image: "/media/logo.png",
        size: "medium",
      })

      renderWithContexts(
        <LogoComponent
          {...getProps({
            appLogo: logoWithRelativeUrl,
            dataTestId: "stSidebarLogo",
          })}
        />,
        {
          libConfigContext: {
            resourceCrossOriginMode: "use-credentials",
          },
        }
      )

      const logo = screen.getByTestId("stSidebarLogo")
      expect(logo).toHaveAttribute("crossOrigin", "use-credentials")
    })
  })
})
