import {
  Prisma,
  type WorkerDeploymentStatus,
  type WorkerInstanceGroupType,
} from "@trigger.dev/database";
import { sqlDatabaseSchema, type PrismaClient, prisma } from "~/db.server";
import { type Organization } from "~/models/organization.server";
import { type Project } from "~/models/project.server";
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
import { type User } from "~/models/user.server";
import { processGitMetadata } from "./BranchesPresenter.server";
import { BranchTrackingConfigSchema, getTrackedBranchForEnvironment } from "~/v3/github";
import {
  VercelProjectIntegrationDataSchema,
  buildVercelDeploymentUrl,
} from "~/v3/vercel/vercelProjectIntegrationSchema";

const pageSize = 20;

export type DeploymentList = Awaited<ReturnType<DeploymentListPresenter["call"]>>;
export type DeploymentListItem = DeploymentList["deployments"][0];

export class DeploymentListPresenter {
  #prismaClient: PrismaClient;

  constructor(prismaClient: PrismaClient = prisma) {
    this.#prismaClient = prismaClient;
  }

  public async call({
    userId,
    projectSlug,
    organizationSlug,
    environmentSlug,
    page = 1,
  }: {
    userId: User["id"];
    projectSlug: Project["slug"];
    organizationSlug: Organization["slug"];
    environmentSlug: string;
    page?: number;
  }) {
    const project = await this.#prismaClient.project.findFirstOrThrow({
      select: {
        id: true,
        environments: {
          select: {
            id: true,
            type: true,
            slug: true,
            orgMember: {
              select: {
                user: {
                  select: {
                    id: true,
                    name: true,
                    displayName: true,
                  },
                },
              },
            },
          },
        },
        connectedGithubRepository: {
          select: {
            branchTracking: true,
            previewDeploymentsEnabled: true,
            repository: {
              select: {
                htmlUrl: true,
                fullName: true,
              },
            },
          },
        },
      },
      where: {
        slug: projectSlug,
        organization: {
          slug: organizationSlug,
          members: {
            some: {
              userId,
            },
          },
        },
      },
    });

    const environment = await findEnvironmentBySlug(project.id, environmentSlug, userId);
    if (!environment) {
      throw new Error(`Environment not found`);
    }

    const totalCount = await this.#prismaClient.workerDeployment.count({
      where: {
        projectId: project.id,
        environmentId: environment.id,
      },
    });

    const labeledDeployments = await this.#prismaClient.workerDeploymentPromotion.findMany({
      where: {
        environmentId: environment.id,
      },
      select: {
        deploymentId: true,
        label: true,
      },
    });

    // Check for Vercel integration before the main query so we can conditionally LEFT JOIN
    let hasVercelIntegration = false;
    let vercelTeamSlug: string | undefined;
    let vercelProjectName: string | undefined;

    const vercelProjectIntegration =
      await this.#prismaClient.organizationProjectIntegration.findFirst({
        where: {
          projectId: project.id,
          deletedAt: null,
          organizationIntegration: {
            service: "VERCEL",
            deletedAt: null,
          },
        },
        select: {
          integrationData: true,
        },
      });

    if (vercelProjectIntegration) {
      const parsed = VercelProjectIntegrationDataSchema.safeParse(
        vercelProjectIntegration.integrationData
      );

      if (parsed.success && parsed.data.vercelTeamSlug) {
        hasVercelIntegration = true;
        vercelTeamSlug = parsed.data.vercelTeamSlug;
        vercelProjectName = parsed.data.vercelProjectName;
      }
    }

    const vercelSelect = hasVercelIntegration
      ? Prisma.sql`, id_dep."integrationDeploymentId"`
      : Prisma.sql``;
    const vercelJoin = hasVercelIntegration
      ? Prisma.sql`LEFT JOIN LATERAL (
        SELECT id_inner."integrationDeploymentId"
        FROM ${sqlDatabaseSchema}."IntegrationDeployment" as id_inner
        WHERE id_inner."deploymentId" = wd."id" AND id_inner."integrationName" = 'vercel'
        ORDER BY id_inner."createdAt" DESC
        LIMIT 1
      ) id_dep ON true`
      : Prisma.sql``;

    const deployments = await this.#prismaClient.$queryRaw<
      {
        id: string;
        shortCode: string;
        version: string;
        runtime: string | null;
        runtimeVersion: string | null;
        status: WorkerDeploymentStatus;
        environmentId: string;
        builtAt: Date | null;
        deployedAt: Date | null;
        tasksCount: bigint | null;
        userId: string | null;
        userName: string | null;
        userDisplayName: string | null;
        userAvatarUrl: string | null;
        type: WorkerInstanceGroupType;
        git: Prisma.JsonValue | null;
        integrationDeploymentId: string | null;
      }[]
    >`
    SELECT
  wd."id",
  wd."shortCode",
  wd."version",
  wd."runtime",
  wd."runtimeVersion",
  (SELECT COUNT(*) FROM ${sqlDatabaseSchema}."BackgroundWorkerTask" WHERE "BackgroundWorkerTask"."workerId" = wd."workerId") AS "tasksCount",
  wd."environmentId",
  wd."status",
  u."id" AS "userId",
  u."name" AS "userName",
  u."displayName" AS "userDisplayName",
  u."avatarUrl" AS "userAvatarUrl",
  wd."builtAt",
  wd."deployedAt",
  wd."type",
  wd."git"
  ${vercelSelect}
FROM
  ${sqlDatabaseSchema}."WorkerDeployment" as wd
LEFT JOIN
  ${sqlDatabaseSchema}."User" as u ON wd."triggeredById" = u."id"
${vercelJoin}
WHERE
  wd."projectId" = ${project.id}
  AND wd."environmentId" = ${environment.id}
ORDER BY
  string_to_array(wd."version", '.')::int[] DESC
LIMIT ${pageSize} OFFSET ${pageSize * (page - 1)};`;

    const { connectedGithubRepository } = project;

    const branchTrackingOrError =
      connectedGithubRepository &&
      BranchTrackingConfigSchema.safeParse(connectedGithubRepository.branchTracking);
    const environmentGitHubBranch =
      branchTrackingOrError && branchTrackingOrError.success
        ? getTrackedBranchForEnvironment(
            branchTrackingOrError.data,
            connectedGithubRepository.previewDeploymentsEnabled,
            {
              type: environment.type,
              branchName: environment.branchName ?? undefined,
            }
          )
        : undefined;

    return {
      currentPage: page,
      totalPages: Math.ceil(totalCount / pageSize),
      hasVercelIntegration,
      connectedGithubRepository: project.connectedGithubRepository ?? undefined,
      environmentGitHubBranch,
      deployments: deployments.map((deployment, index) => {
        const label = labeledDeployments.find(
          (labeledDeployment) => labeledDeployment.deploymentId === deployment.id
        );

        let vercelDeploymentUrl: string | null = null;
        if (
          hasVercelIntegration &&
          deployment.integrationDeploymentId &&
          vercelTeamSlug &&
          vercelProjectName
        ) {
          vercelDeploymentUrl = buildVercelDeploymentUrl(
            vercelTeamSlug,
            vercelProjectName,
            deployment.integrationDeploymentId
          );
        }

        return {
          id: deployment.id,
          shortCode: deployment.shortCode,
          version: deployment.version,
          runtime: deployment.runtime,
          runtimeVersion: deployment.runtimeVersion,
          status: deployment.status,
          builtAt: deployment.builtAt,
          deployedAt: deployment.deployedAt,
          tasksCount: deployment.tasksCount ? Number(deployment.tasksCount) : null,
          label: label?.label,
          isBuilt: !!deployment.builtAt,
          isCurrent: label?.label === "current",
          isDeployed: deployment.status === "DEPLOYED",
          isLatest: page === 1 && index === 0,
          type: deployment.type,
          environment: {
            id: environment.id,
            type: environment.type,
            slug: environment.slug,
          },
          deployedBy: deployment.userId
            ? {
                id: deployment.userId,
                name: deployment.userName,
                displayName: deployment.userDisplayName,
                avatarUrl: deployment.userAvatarUrl,
              }
            : undefined,
          git: processGitMetadata(deployment.git),
          vercelDeploymentUrl,
        };
      }),
    };
  }

  public async findPageForVersion({
    userId,
    projectSlug,
    organizationSlug,
    environmentSlug,
    version,
  }: {
    userId: User["id"];
    projectSlug: Project["slug"];
    organizationSlug: Organization["slug"];
    environmentSlug: string;
    version: string;
  }) {
    const project = await this.#prismaClient.project.findFirstOrThrow({
      select: {
        id: true,
      },
      where: {
        slug: projectSlug,
        organization: {
          slug: organizationSlug,
          members: {
            some: {
              userId,
            },
          },
        },
      },
    });

    const environment = await findEnvironmentBySlug(project.id, environmentSlug, userId);
    if (!environment) {
      throw new Error(`Environment not found`);
    }

    // Find how many deployments have been made since this version
    const deploymentsSinceVersion = await this.#prismaClient.$queryRaw<{ count: bigint }[]>`
      SELECT COUNT(*) as count
      FROM ${sqlDatabaseSchema}."WorkerDeployment"
      WHERE "projectId" = ${project.id}
        AND "environmentId" = ${environment.id}
        AND string_to_array(version, '.')::int[] > string_to_array(${version}, '.')::int[]
    `;

    const count = Number(deploymentsSinceVersion[0].count);
    return Math.floor(count / pageSize) + 1;
  }
}
