// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import "./styles/common.css";
import React, { useState, useEffect, useMemo, useRef } from "react";
import { SortingState } from "@tanstack/react-table";
import { useQueryClient } from "@tanstack/react-query";
import {
  fetchTestAnalysis,
  convertToTestDetailsData,
} from "./utils/fetch_runs_data";
import {
  CONCURRENCY_BACKGROUND,
  CONCURRENCY_FOREGROUND,
  TestRunInfo,
} from "./data_defs";
import { Menu } from "./menu.tsx";
import { VirtualizedTable } from "./virtualized_table.tsx";
import { Link, useParams, useSearchParams } from "react-router-dom";
import { SearchInput } from "./search";
import {
  createColumns,
  defaultSorting,
  columnWidthMap,
} from "./table_defs/test_details";
import { test_filters } from "./branch_quick_filters.tsx";
import { VerifyPrompt } from "./verify_prompt.tsx";
import { useVerifyGetAll } from "./contexts/verify_get_all_context.tsx";

export function TestDetails(): React.JSX.Element {
  const [searchParams, setSearchParams] = useSearchParams();
  const branchFromUrl = searchParams.get("branchFilter") || "main";
  const [branchFilter, setBranchFilterState] = useState<string>(branchFromUrl);
  const [searchFilter, setSearchFilter] = useState<string>("");
  const [tableData, setTableData] = useState<TestRunInfo[]>([]);
  const [fetchedCount, setFetchedCount] = useState<number>(0);
  const [totalToFetch, setTotalToFetch] = useState<number | null>(null);
  const queryClient = useQueryClient();
  const { verified, setVerified } = useVerifyGetAll();

  const { architecture: archParam, testName: encodedTestName } = useParams();
  // Build full test name depending on whether new (architecture + remainder) or legacy route
  const architecture = archParam ? decodeURIComponent(archParam) : "";
  const testNameRemainder = encodedTestName
    ? decodeURIComponent(encodedTestName)
    : "";
  const fullTestName = architecture + "/" + testNameRemainder;

  // Track component mount state for dynamic concurrency control
  const concurrencyRef = useRef(CONCURRENCY_FOREGROUND);

  // Update concurrency based on mount state
  useEffect(() => {
    concurrencyRef.current = CONCURRENCY_FOREGROUND;
    return () => {
      concurrencyRef.current = CONCURRENCY_BACKGROUND;
    };
  }, []);

  // Sync state with URL on mount and when URL changes
  useEffect(() => {
    setBranchFilterState(branchFromUrl);
  }, [branchFromUrl]);

  // Update both state and URL when branch filter changes
  const setBranchFilter = (branch: string) => {
    setBranchFilterState(branch);
    const newParams = new URLSearchParams(searchParams);
    newParams.set("branchFilter", branch);
    setSearchParams(newParams, { replace: true });
  };

  // Fetch run details for the selected branch
  useEffect(() => {
    setTableData([]);

    // Create an abort controller for this effect
    const abortController = new AbortController();

    if (branchFilter === "all" && !verified) {
      setFetchedCount(0);
      setTotalToFetch(0);
      return;
    }

    // Fetch test analysis (which returns the test mapping)
    fetchTestAnalysis(
      branchFilter,
      queryClient,
      (fetched, total) => {
        if (!abortController.signal.aborted) {
          setFetchedCount(fetched);
          setTotalToFetch(total);
        }
      },
      () => concurrencyRef.current, // Dynamic concurrency
      abortController.signal,
    )
      .then((testMapping) => {
        if (!abortController.signal.aborted) {
          setTableData(convertToTestDetailsData(testMapping, fullTestName));
        }
      })
      .catch((err) => {
        // Ignore abort errors, log others
        if (err.name !== "AbortError" && !abortController.signal.aborted) {
          console.error("Error fetching test analysis:", err);
        }
      });

    return () => {
      // This only runs when the effect is being cleaned up (i.e., dependencies changed or component unmounted)
      abortController.abort();
    };
  }, [fullTestName, branchFilter, queryClient, verified]);

  // Get the table definition (columns and default sorting)
  const [sorting, setSorting] = useState<SortingState>(defaultSorting);
  const columns = useMemo(() => createColumns(fullTestName), [fullTestName]);
  const filteredTableData = useMemo(
    () => filterTests(tableData, searchFilter),
    [tableData, searchFilter],
  );

  return (
    <div className="common-page-display">
      <div className="common-page-header">
        <TestDetailsHeader
          architecture={architecture}
          testNameRemainder={testNameRemainder}
          fullTestName={fullTestName}
          branchFilter={branchFilter}
          setBranchFilter={setBranchFilter}
          searchFilter={searchFilter}
          setSearchFilter={setSearchFilter}
          resultCount={filteredTableData.length}
          fetchedCount={fetchedCount}
          totalToFetch={totalToFetch}
        />
      </div>
      {!verified && branchFilter === "all" ? (
        <VerifyPrompt onOk={() => setVerified(true)} />
      ) : (
        <VirtualizedTable
          data={filteredTableData}
          columns={columns}
          sorting={sorting}
          columnWidthMap={columnWidthMap}
          onSortingChange={setSorting}
        />
      )}
    </div>
  );
}

interface TestDetailsHeaderProps {
  architecture: string;
  testNameRemainder: string;
  fullTestName: string;
  branchFilter: string;
  setBranchFilter: (branch: string) => void;
  searchFilter: string;
  setSearchFilter: (filter: string) => void;
  resultCount: number;
  fetchedCount: number;
  totalToFetch: number | null;
}

export function TestDetailsHeader({
  architecture,
  testNameRemainder,
  fullTestName,
  branchFilter,
  setBranchFilter,
  searchFilter,
  setSearchFilter,
  resultCount,
  fetchedCount,
  totalToFetch,
}: TestDetailsHeaderProps): React.JSX.Element {
  const encodedArchitecture = encodeURIComponent(architecture);
  const encodedTestName = encodeURIComponent(testNameRemainder);

  return (
    <>
      <div
        className="common-header-left"
        style={{ minWidth: 0, flex: 1, display: "flex" }}
      >
        <div
          className="common-header-title"
          style={{
            minWidth: 0,
          }}
        >
          <Menu />
          <Link
            to={`/tests?branchFilter=${encodeURIComponent(branchFilter)}`}
            className="common-header-path"
          >
            Tests
          </Link>
          <span>/</span>
          <Link
            to={`/tests/${encodedArchitecture}/${encodedTestName}`}
            className="common-header-path-long"
            title={fullTestName}
          >
            {testNameRemainder}
          </Link>
          {architecture && (
            <div className="common-sub-header">{architecture}</div>
          )}
        </div>
        <div className="common-header-filter-buttons">
          {test_filters.map((branch) => (
            <button
              key={branch}
              className={`common-header-filter-btn ${branchFilter === branch ? "active" : ""}`}
              onClick={() => setBranchFilter(branch)}
            >
              {branch}
            </button>
          ))}
        </div>
        {totalToFetch === null && (
          <div className="header-loading-indicator">
            <div className="header-loading-spinner"></div>
            <div className="header-loading-text">Fetching runs ...</div>
          </div>
        )}
        {fetchedCount !== totalToFetch && totalToFetch !== null && (
          <div className="header-loading-indicator">
            <div className="header-loading-spinner"></div>
            <div className="header-loading-text">
              Analysed {fetchedCount}/{totalToFetch}
            </div>
          </div>
        )}
      </div>
      <div className="common-header-right">
        <SearchInput value={searchFilter} onChange={setSearchFilter} />
        <span className="common-result-count">{resultCount} tests</span>
      </div>
    </>
  );
}

/**
 * filterTests filters the list of tests based on search terms.
 *
 * - Search string is split into terms (by whitespace), and each test is checked
 *   to see if ALL terms are present.
 * - The searchable fields include: run number and status.
 * - The filtering is case-insensitive.
 */
function filterTests(
  tests: TestRunInfo[],
  searchFilter: string,
): TestRunInfo[] {
  const terms = searchFilter.trim().toLowerCase().split(/\s+/).filter(Boolean);
  if (terms.length === 0) return tests;
  return tests.filter((test) => {
    // Search in name and status fields
    const haystack = `${test.runNumber} ${test.status}`.toLowerCase();
    return terms.every((term) => haystack.includes(term));
  });
}
