import log from "electron-log";

import { createTestOnlyLoggedHandler } from "./safe_handle";
import { createTypedHandler } from "./base";
import { handleNeonOAuthReturn } from "../../neon_admin/neon_return_handler";
import {
  getNeonClient,
  getNeonErrorMessage,
  getNeonOrganizationId,
} from "../../neon_admin/neon_management_client";
import { neonContracts, type NeonBranch } from "../types/neon";
import { db } from "../../db";
import { apps } from "../../db/schema";
import { eq } from "drizzle-orm";
import { EndpointType } from "@neondatabase/api-client";
import { retryOnLocked } from "../utils/retryOnLocked";
import { DyadError, DyadErrorKind } from "@/errors/dyad_error";

export const logger = log.scope("neon_handlers");

const testOnlyHandle = createTestOnlyLoggedHandler(logger);

export function registerNeonHandlers() {
  // Do not use log handler because there's sensitive data in the response
  createTypedHandler(neonContracts.createProject, async (_, params) => {
    const { name, appId } = params;
    const neonClient = await getNeonClient();

    logger.info(`Creating Neon project: ${name} for app ${appId}`);

    try {
      // Get the organization ID
      const orgId = await getNeonOrganizationId();

      // Create project with retry on locked errors
      const response = await retryOnLocked(
        () =>
          neonClient.createProject({
            project: {
              name: name,
              org_id: orgId,
            },
          }),
        `Create project ${name} for app ${appId}`,
      );

      if (!response.data.project) {
        throw new DyadError(
          "Failed to create project: No project data returned.",
          DyadErrorKind.External,
        );
      }

      const project = response.data.project;
      const developmentBranch = response.data.branch;

      const previewBranchResponse = await retryOnLocked(
        () =>
          neonClient.createProjectBranch(project.id, {
            endpoints: [{ type: EndpointType.ReadOnly }],
            branch: {
              name: "preview",
              parent_id: developmentBranch.id,
            },
          }),
        `Create preview branch for project ${project.id}`,
      );

      if (
        !previewBranchResponse.data.branch ||
        !previewBranchResponse.data.connection_uris
      ) {
        throw new Error(
          "Failed to create preview branch: No branch data returned.",
        );
      }

      const previewBranch = previewBranchResponse.data.branch;

      // Store project and branch info in the app's DB row
      await db
        .update(apps)
        .set({
          neonProjectId: project.id,
          neonDevelopmentBranchId: developmentBranch.id,
          neonPreviewBranchId: previewBranch.id,
        })
        .where(eq(apps.id, appId));

      logger.info(
        `Successfully created Neon project: ${project.id} and development branch: ${developmentBranch.id} for app ${appId}`,
      );
      return {
        id: project.id,
        name: project.name,
        connectionString: response.data.connection_uris[0].connection_uri,
        branchId: developmentBranch.id,
      };
    } catch (error: any) {
      const errorMessage = getNeonErrorMessage(error);
      const message = `Failed to create Neon project for app ${appId}: ${errorMessage}`;
      logger.error(message);
      throw new Error(message);
    }
  });

  createTypedHandler(neonContracts.getProject, async (_, params) => {
    const { appId } = params;
    logger.info(`Getting Neon project info for app ${appId}`);

    try {
      // Get the app from the database to find the neonProjectId and neonBranchId
      const app = await db
        .select()
        .from(apps)
        .where(eq(apps.id, appId))
        .limit(1);

      if (app.length === 0) {
        throw new DyadError(
          `App with ID ${appId} not found`,
          DyadErrorKind.NotFound,
        );
      }

      const appData = app[0];
      if (!appData.neonProjectId) {
        throw new DyadError(
          `No Neon project found for app ${appId}`,
          DyadErrorKind.External,
        );
      }

      const neonClient = await getNeonClient();
      console.log("PROJECT ID", appData.neonProjectId);

      // Get project info
      const projectResponse = await neonClient.getProject(
        appData.neonProjectId,
      );

      if (!projectResponse.data.project) {
        throw new DyadError(
          "Failed to get project: No project data returned.",
          DyadErrorKind.External,
        );
      }

      const project = projectResponse.data.project;

      // Get list of branches
      const branchesResponse = await neonClient.listProjectBranches({
        projectId: appData.neonProjectId,
      });

      if (!branchesResponse.data.branches) {
        throw new DyadError(
          "Failed to get branches: No branch data returned.",
          DyadErrorKind.External,
        );
      }

      // Map branches to our format
      const branches: NeonBranch[] = branchesResponse.data.branches.map(
        (branch) => {
          let type: "production" | "development" | "snapshot" | "preview";

          if (branch.default) {
            type = "production";
          } else if (branch.id === appData.neonDevelopmentBranchId) {
            type = "development";
          } else if (branch.id === appData.neonPreviewBranchId) {
            type = "preview";
          } else {
            type = "snapshot";
          }

          // Find parent branch name if parent_id exists
          let parentBranchName: string | undefined;
          if (branch.parent_id) {
            const parentBranch = branchesResponse.data.branches?.find(
              (b) => b.id === branch.parent_id,
            );
            parentBranchName = parentBranch?.name;
          }

          return {
            type,
            branchId: branch.id,
            branchName: branch.name,
            lastUpdated: branch.updated_at,
            parentBranchId: branch.parent_id,
            parentBranchName,
          };
        },
      );

      logger.info(`Successfully retrieved Neon project info for app ${appId}`);

      return {
        projectId: project.id,
        projectName: project.name,
        orgId: project.org_id ?? "<unknown_org_id>",
        branches,
      };
    } catch (error) {
      logger.error(`Failed to get Neon project info for app ${appId}:`, error);
      throw error;
    }
  });

  testOnlyHandle("neon:fake-connect", async (event) => {
    // Call handleNeonOAuthReturn with fake data
    handleNeonOAuthReturn({
      token: "fake-neon-access-token",
      refreshToken: "fake-neon-refresh-token",
      expiresIn: 3600, // 1 hour
    });
    logger.info("Called handleNeonOAuthReturn with fake data during testing.");

    // Simulate the deep link event
    event.sender.send("deep-link-received", {
      type: "neon-oauth-return",
      url: "https://oauth.dyad.sh/api/integrations/neon/login",
    });
    logger.info("Sent fake neon deep-link-received event during testing.");
  });
}
