import { useCallback, useEffect, useState } from "react";
import RepositoryTable from "./RepositoryTable";
import type { MirrorJob, Repository } from "@/lib/db/schema";
import { useAuth } from "@/hooks/useAuth";
import {
  repoStatusEnum,
  type AddRepositoriesApiRequest,
  type AddRepositoriesApiResponse,
  type RepositoryApiResponse,
  type RepoStatus,
} from "@/types/Repository";
import { apiRequest } from "@/lib/utils";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "../ui/select";
import { Button } from "@/components/ui/button";
import { Search, RefreshCw, FlipHorizontal } from "lucide-react";
import type { MirrorRepoRequest, MirrorRepoResponse } from "@/types/mirror";
import { useSSE } from "@/hooks/useSEE";
import { useFilterParams } from "@/hooks/useFilterParams";
import { toast } from "sonner";
import type { SyncRepoRequest, SyncRepoResponse } from "@/types/sync";
import { OwnerCombobox, OrganizationCombobox } from "./RepositoryComboboxes";
import type { RetryRepoRequest, RetryRepoResponse } from "@/types/retry";
import AddRepositoryDialog from "./AddRepositoryDialog";

import { useLiveRefresh } from "@/hooks/useLiveRefresh";
import { useConfigStatus } from "@/hooks/useConfigStatus";
import { useNavigation } from "@/components/layout/MainLayout";

export default function Repository() {
  const [repositories, setRepositories] = useState<Repository[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const { user } = useAuth();
  const { registerRefreshCallback } = useLiveRefresh();
  const { isGitHubConfigured } = useConfigStatus();
  const { navigationKey } = useNavigation();
  const { filter, setFilter } = useFilterParams({
    searchTerm: "",
    status: "",
    organization: "",
    owner: "",
  });
  const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);

  // Read organization filter from URL when component mounts
  useEffect(() => {
    const urlParams = new URLSearchParams(window.location.search);
    const orgParam = urlParams.get("organization");

    if (orgParam) {
      setFilter((prev) => ({ ...prev, organization: orgParam }));
    }
  }, [setFilter]);

  const [loadingRepoIds, setLoadingRepoIds] = useState<Set<string>>(new Set()); // this is used when the api actions are performed

  // Create a stable callback using useCallback
  const handleNewMessage = useCallback((data: MirrorJob) => {
    if (data.repositoryId) {
      setRepositories((prevRepos) =>
        prevRepos.map((repo) =>
          repo.id === data.repositoryId
            ? { ...repo, status: data.status, details: data.details }
            : repo
        )
      );
    }

    console.log("Received new log:", data);
  }, []);

  // Use the SSE hook
  const { connected } = useSSE({
    userId: user?.id,
    onMessage: handleNewMessage,
  });

  const fetchRepositories = useCallback(async () => {
    if (!user?.id) return;

    // Don't fetch repositories if GitHub is not configured or still loading config
    if (!isGitHubConfigured) {
      setIsLoading(false);
      return false;
    }

    try {
      setIsLoading(true);

      const response = await apiRequest<RepositoryApiResponse>(
        `/github/repositories?userId=${user.id}`,
        {
          method: "GET",
        }
      );

      if (response.success) {
        setRepositories(response.repositories);
        return true;
      } else {
        toast.error(response.error || "Error fetching repositories");
        return false;
      }
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "Error fetching repositories"
      );
      return false;
    } finally {
      setIsLoading(false);
    }
  }, [user?.id, isGitHubConfigured]); // Only depend on user.id, not entire user object

  useEffect(() => {
    // Reset loading state when component becomes active
    setIsLoading(true);
    fetchRepositories();
  }, [fetchRepositories, navigationKey]); // Include navigationKey to trigger on navigation

  // Register with global live refresh system
  useEffect(() => {
    // Only register for live refresh if GitHub is configured
    if (!isGitHubConfigured) {
      return;
    }

    const unregister = registerRefreshCallback(() => {
      fetchRepositories();
    });

    return unregister;
  }, [registerRefreshCallback, fetchRepositories, isGitHubConfigured]);

  const handleRefresh = async () => {
    const success = await fetchRepositories();
    if (success) {
      toast.success("Repositories refreshed successfully.");
    }
  };

  const handleMirrorRepo = async ({ repoId }: { repoId: string }) => {
    try {
      if (!user || !user.id) {
        return;
      }

      setLoadingRepoIds((prev) => new Set(prev).add(repoId));

      const reqPayload: MirrorRepoRequest = {
        userId: user.id,
        repositoryIds: [repoId],
      };

      const response = await apiRequest<MirrorRepoResponse>(
        "/job/mirror-repo",
        {
          method: "POST",
          data: reqPayload,
        }
      );

      if (response.success) {
        toast.success(`Mirroring started for repository ID: ${repoId}`);
        setRepositories((prevRepos) =>
          prevRepos.map((repo) => {
            const updated = response.repositories.find((r) => r.id === repo.id);
            return updated ? updated : repo;
          })
        );
      } else {
        toast.error(response.error || "Error starting mirror job");
      }
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "Error starting mirror job"
      );
    } finally {
      setLoadingRepoIds((prev) => {
        const newSet = new Set(prev);
        newSet.delete(repoId);
        return newSet;
      });
    }
  };

  const handleMirrorAllRepos = async () => {
    try {
      if (!user || !user.id || repositories.length === 0) {
        return;
      }

      // Filter out repositories that are already mirroring to avoid duplicate operations. also filter out mirrored (mirrored can be synced and not mirrored again)
      const eligibleRepos = repositories.filter(
        (repo) =>
          repo.status !== "mirroring" && repo.status !== "mirrored" && repo.id //not ignoring failed ones because we want to retry them if not mirrored. if mirrored, gitea fucnion handlers will silently ignore them
      );

      if (eligibleRepos.length === 0) {
        toast.info("No eligible repositories to mirror");
        return;
      }

      // Get all repository IDs
      const repoIds = eligibleRepos.map((repo) => repo.id as string);

      // Set loading state for all repositories being mirrored
      setLoadingRepoIds((prev) => {
        const newSet = new Set(prev);
        repoIds.forEach((id) => newSet.add(id));
        return newSet;
      });

      const reqPayload: MirrorRepoRequest = {
        userId: user.id,
        repositoryIds: repoIds,
      };

      const response = await apiRequest<MirrorRepoResponse>(
        "/job/mirror-repo",
        {
          method: "POST",
          data: reqPayload,
        }
      );

      if (response.success) {
        toast.success(`Mirroring started for ${repoIds.length} repositories`);
        setRepositories((prevRepos) =>
          prevRepos.map((repo) => {
            const updated = response.repositories.find((r) => r.id === repo.id);
            return updated ? updated : repo;
          })
        );
      } else {
        toast.error(response.error || "Error starting mirror jobs");
      }
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "Error starting mirror jobs"
      );
    } finally {
      // Reset loading states - we'll let the SSE updates handle status changes
      setLoadingRepoIds(new Set());
    }
  };

  const handleSyncRepo = async ({ repoId }: { repoId: string }) => {
    try {
      if (!user || !user.id) {
        return;
      }

      setLoadingRepoIds((prev) => new Set(prev).add(repoId));

      const reqPayload: SyncRepoRequest = {
        userId: user.id,
        repositoryIds: [repoId],
      };

      const response = await apiRequest<SyncRepoResponse>("/job/sync-repo", {
        method: "POST",
        data: reqPayload,
      });

      if (response.success) {
        toast.success(`Syncing started for repository ID: ${repoId}`);
        setRepositories((prevRepos) =>
          prevRepos.map((repo) => {
            const updated = response.repositories.find((r) => r.id === repo.id);
            return updated ? updated : repo;
          })
        );
      } else {
        toast.error(response.error || "Error starting sync job");
      }
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "Error starting sync job"
      );
    } finally {
      setLoadingRepoIds((prev) => {
        const newSet = new Set(prev);
        newSet.delete(repoId);
        return newSet;
      });
    }
  };

  const handleRetryRepoAction = async ({ repoId }: { repoId: string }) => {
    try {
      if (!user || !user.id) {
        return;
      }

      setLoadingRepoIds((prev) => new Set(prev).add(repoId));

      const reqPayload: RetryRepoRequest = {
        userId: user.id,
        repositoryIds: [repoId],
      };

      const response = await apiRequest<RetryRepoResponse>("/job/retry-repo", {
        method: "POST",
        data: reqPayload,
      });

      if (response.success) {
        toast.success(`Retrying job for repository ID: ${repoId}`);
        setRepositories((prevRepos) =>
          prevRepos.map((repo) => {
            const updated = response.repositories.find((r) => r.id === repo.id);
            return updated ? updated : repo;
          })
        );
      } else {
        toast.error(response.error || "Error retrying job");
      }
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "Error retrying job"
      );
    } finally {
      setLoadingRepoIds((prev) => {
        const newSet = new Set(prev);
        newSet.delete(repoId);
        return newSet;
      });
    }
  };

  const handleAddRepository = async ({
    repo,
    owner,
  }: {
    repo: string;
    owner: string;
  }) => {
    try {
      if (!user || !user.id) {
        return;
      }

      const reqPayload: AddRepositoriesApiRequest = {
        userId: user.id,
        repo,
        owner,
      };

      const response = await apiRequest<AddRepositoriesApiResponse>(
        "/sync/repository",
        {
          method: "POST",
          data: reqPayload,
        }
      );

      if (response.success) {
        toast.success(`Repository added successfully`);
        setRepositories((prevRepos) => [...prevRepos, response.repository]);

        await fetchRepositories();

        setFilter((prev) => ({
          ...prev,
          searchTerm: repo,
        }));
      } else {
        toast.error(response.error || "Error adding repository");
      }
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "Error adding repository"
      );
    }
  };

  // Get unique owners and organizations for comboboxes
  const ownerOptions = Array.from(
    new Set(
      repositories.map((repo) => repo.owner).filter((v): v is string => !!v)
    )
  ).sort();
  const orgOptions = Array.from(
    new Set(
      repositories
        .map((repo) => repo.organization)
        .filter((v): v is string => !!v)
    )
  ).sort();

  return (
    <div className="flex flex-col gap-y-8">
      {/* Combine search and actions into a single flex row */}
      <div className="flex flex-row items-center gap-4 w-full flex-wrap">
        <div className="relative flex-grow min-w-[180px]">
          <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
          <input
            type="text"
            placeholder="Search repositories..."
            className="pl-8 h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
            value={filter.searchTerm}
            onChange={(e) =>
              setFilter((prev) => ({ ...prev, searchTerm: e.target.value }))
            }
          />
        </div>

        {/* Owner Combobox */}
        <OwnerCombobox
          options={ownerOptions}
          value={filter.owner || ""}
          onChange={(owner: string) =>
            setFilter((prev) => ({ ...prev, owner }))
          }
        />

        {/* Organization Combobox */}
        <OrganizationCombobox
          options={orgOptions}
          value={filter.organization || ""}
          onChange={(organization: string) =>
            setFilter((prev) => ({ ...prev, organization }))
          }
        />

        <Select
          value={filter.status || "all"}
          onValueChange={(value) =>
            setFilter((prev) => ({
              ...prev,
              status: value === "all" ? "" : (value as RepoStatus),
            }))
          }
        >
          <SelectTrigger className="w-[140px] h-9 max-h-9">
            <SelectValue placeholder="All Status" />
          </SelectTrigger>
          <SelectContent>
            {["all", ...repoStatusEnum.options].map((status) => (
              <SelectItem key={status} value={status}>
                {status === "all"
                  ? "All Status"
                  : status.charAt(0).toUpperCase() + status.slice(1)}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>

        <Button
          variant="outline"
          size="icon"
          onClick={handleRefresh}
          title="Refresh repositories"
        >
          <RefreshCw className="h-4 w-4" />
        </Button>

        <Button
          variant="default"
          onClick={handleMirrorAllRepos}
          disabled={isLoading || loadingRepoIds.size > 0}
        >
          <FlipHorizontal className="h-4 w-4 mr-2" />
          Mirror All
        </Button>
      </div>

      {!isGitHubConfigured ? (
        <div className="flex flex-col items-center justify-center p-8 border border-dashed rounded-md">
          <h3 className="text-xl font-semibold mb-2">GitHub Not Configured</h3>
          <p className="text-muted-foreground text-center mb-4">
            You need to configure your GitHub credentials before you can fetch and mirror repositories.
          </p>
          <Button
            variant="default"
            onClick={() => {
              window.history.pushState({}, '', '/config');
              // We need to trigger a page change event for the navigation system
              window.dispatchEvent(new PopStateEvent('popstate'));
            }}
          >
            Go to Configuration
          </Button>
        </div>
      ) : (
        <RepositoryTable
          repositories={repositories}
          isLoading={isLoading || !connected}
          filter={filter}
          setFilter={setFilter}
          onMirror={handleMirrorRepo}
          onSync={handleSyncRepo}
          onRetry={handleRetryRepoAction}
          loadingRepoIds={loadingRepoIds}
        />
      )}

      <AddRepositoryDialog
        onAddRepository={handleAddRepository}
        isDialogOpen={isDialogOpen}
        setIsDialogOpen={setIsDialogOpen}
      />
    </div>
  );
}
