import { prisma } from "@codeflash-ai/common"
import { Prisma } from "@prisma/client"
import { type Octokit } from "octokit"
import { createNewBranchFromDiffContents } from "../github/create-pr-from-diffcontents.js"
import { githubApp } from "../github/github-app.js"
import { isUserCollaborator } from "../github/github-utils.js"
import { userNickname } from "../auth0-mgmt.js"
import { PlainTextStagingStrategy } from "./plain-text-staging-strategy.js"
import {
  StagingStorageStrategy,
  StagingStorageContext,
  StagingStorageResult,
  parseSpeedupValue,
  buildMergedMetadata,
} from "./staging-storage-strategy.js"
import * as Sentry from "@sentry/node"
export interface GitBranchStagingDependencies {
  prisma: {
    optimization_events: {
      update: typeof prisma.optimization_events.update
      findFirst: typeof prisma.optimization_events.findFirst
    }
    repositories: {
      findUnique: typeof prisma.repositories.findUnique
    }
  }
  getInstallationOctokit: (installationId: number) => Promise<Octokit>
  createNewBranchFromDiffContents: typeof createNewBranchFromDiffContents
  userNickname: typeof userNickname
  isUserCollaborator: typeof isUserCollaborator
}

let dependencies: GitBranchStagingDependencies = {
  prisma: {
    optimization_events: {
      update: prisma.optimization_events.update,
      findFirst: prisma.optimization_events.findFirst,
    },
    repositories: {
      findUnique: prisma.repositories.findUnique,
    },
  },
  getInstallationOctokit: (installationId: number) =>
    githubApp.getInstallationOctokit(installationId) as Promise<Octokit>,
  createNewBranchFromDiffContents,
  userNickname,
  isUserCollaborator,
}

export function setGitBranchStagingDependencies(newDependencies: GitBranchStagingDependencies) {
  dependencies = newDependencies
}

export function resetGitBranchStagingDependencies() {
  dependencies = {
    prisma: {
      optimization_events: {
        update: prisma.optimization_events.update,
        findFirst: prisma.optimization_events.findFirst,
      },
      repositories: {
        findUnique: prisma.repositories.findUnique,
      },
    },
    getInstallationOctokit: (installationId: number) =>
      githubApp.getInstallationOctokit(installationId) as Promise<Octokit>,
    createNewBranchFromDiffContents,
    userNickname,
    isUserCollaborator,
  }
}

export class GitBranchStagingStrategy extends StagingStorageStrategy {
  private plainTextFallback = new PlainTextStagingStrategy()

  getStorageType(): "git_branch" {
    return "git_branch"
  }

  private async fallbackToPlainText(context: StagingStorageContext): Promise<StagingStorageResult> {
    return this.plainTextFallback.save(context)
  }

  async save(context: StagingStorageContext): Promise<StagingStorageResult> {
    const {
      userId,
      traceId,
      baseBranch,
      owner,
      repo,
      diffContents,
      prCommentFields,
      metadata,
      originalLineProfiler,
      optimizedLineProfiler,
    } = context

    // Validate required git fields - fall back to plain text if missing
    if (!owner || !repo) {
      return this.fallbackToPlainText(context)
    }

    if (!diffContents || diffContents.size === 0) {
      return {
        success: false,
        storageType: "git_branch",
        error: "Missing diffContents for git branch storage",
      }
    }

    const {
      function_name,
      file_path,
      speedup_x: speedupXRaw,
      speedup_pct: speedupPctRaw,
    } = prCommentFields

    const speedupX = parseSpeedupValue(speedupXRaw, "x")
    if (speedupX === null) {
      return { success: false, storageType: "git_branch", error: "Invalid speedup_x value" }
    }

    const speedupPct = speedupPctRaw ? parseSpeedupValue(speedupPctRaw, "%") : undefined
    if (speedupPctRaw && speedupPct === null) {
      return { success: false, storageType: "git_branch", error: "Invalid speedup_pct value" }
    }

    try {
      const existingEvent = await dependencies.prisma.optimization_events.findFirst({
        where: { trace_id: traceId, user_id: userId },
        select: { metadata: true },
      })

      if (!existingEvent) {
        return {
          success: false,
          storageType: "git_branch",
          error: "No optimization event found for the given traceId",
        }
      }

      // Get repository and installation info
      const fullName = `${owner}/${repo}`
      const repository = await dependencies.prisma.repositories.findUnique({
        where: { full_name: fullName },
      })

      if (!repository || !repository.installation_id) {
        // No GitHub App installation - fall back to plain text
        return this.fallbackToPlainText(context)
      }

      const installationOctokit = await dependencies.getInstallationOctokit(
        Number(repository.installation_id),
      )

      const nickname = await dependencies.userNickname(userId)
      if (nickname == null) {
        return {
          success: false,
          storageType: "git_branch",
          error: "unauthorized",
        }
      }

      const isCollaborator = await dependencies.isUserCollaborator(
        installationOctokit,
        owner,
        repo,
        nickname,
      )

      if (!isCollaborator) {
        console.log(`[git-branch-staging] ${nickname} is not a collaborator on ${owner}/${repo}`)
        return {
          success: false,
          storageType: "git_branch",
          error: `User is not a collaborator on ${owner}/${repo}`,
        }
      }

      // Generate staging branch name
      const stagingBranchName = this.generateStagingBranchName(function_name as string, traceId)

      // Create the staging branch with the diff contents
      const branchCreated = await dependencies.createNewBranchFromDiffContents(
        installationOctokit as Octokit,
        owner,
        repo,
        stagingBranchName,
        baseBranch,
        diffContents,
        `Codeflash staging: optimize ${function_name}`,
      )

      if (!branchCreated) {
        // Branch creation failed - fall back to plain text
        return this.fallbackToPlainText(context)
      }

      // Build metadata without diffContents (stored in git branch)
      const mergedMetadata = buildMergedMetadata(
        existingEvent.metadata,
        metadata,
        {
          storageType: "git_branch",
          stagingBranchName,
        },
        { originalLineProfiler, optimizedLineProfiler },
      )

      await dependencies.prisma.optimization_events.update({
        where: { trace_id: traceId },
        data: {
          function_name: function_name as string,
          file_path: file_path as string,
          speedup_x: speedupX,
          speedup_pct: speedupPct,
          metadata: mergedMetadata as Prisma.InputJsonValue,
          is_optimization_found: true,
          is_staging: true,
          staging_storage_type: "git_branch",
          baseBranch: baseBranch,
          event_type: "no-pr",
        },
      })

      return {
        success: true,
        storageType: "git_branch",
        branchName: stagingBranchName,
      }
    } catch (error) {
      Sentry.withScope(scope => {
        scope.setTag("storage_type", "git_branch")
        scope.setTag("component", "GitBranchStagingStrategy")

        scope.setContext("staging_context", {
          traceId,
          userId,
          owner,
          repo,
          baseBranch,
          function_name,
          file_path,
        })

        scope.setLevel("error")

        Sentry.captureException(error)
      })

      return this.fallbackToPlainText(context)
    }
  }

  private generateStagingBranchName(functionName: string, traceId: string): string {
    const sanitizedFunctionName = functionName.replace(/[^a-zA-Z0-9_-]/g, "-").substring(0, 50)
    const shortTraceId = traceId.substring(0, 8)
    const timestamp = Date.now().toString(36)
    return `codeflash-staging/${sanitizedFunctionName}-${shortTraceId}-${timestamp}`
  }
}
