import path from "path"
import { Effect } from "effect"
import { UI } from "../ui"
import type { SessionID } from "../../session/schema"
import { MessageID, PartID } from "../../session/schema"
import { ProviderID, ModelID } from "../../provider/schema"
import { MessageV2 } from "../../session/message-v2"
import { AppRuntime } from "@/effect/app-runtime"
import { Bus } from "../../bus"
import { SessionPrompt } from "@/session/prompt"
import { extractResponseText, formatPromptTooLargeError } from "./github-util"
import type { PromptFile } from "./github-types"
import type {
  IssueCommentEvent,
  IssuesEvent,
  PullRequestReviewCommentEvent,
  WorkflowDispatchEvent,
  WorkflowRunEvent,
  PullRequestEvent,
} from "@octokit/webhooks-types"

type Payload =
  | IssueCommentEvent
  | IssuesEvent
  | PullRequestReviewCommentEvent
  | WorkflowDispatchEvent
  | WorkflowRunEvent
  | PullRequestEvent

export function isIssueCommentEvent(
  event: Payload,
): event is IssueCommentEvent {
  return "issue" in event && "comment" in event
}

export function getReviewCommentContext(
  eventName: string,
  payload: Payload,
) {
  if (eventName !== "pull_request_review_comment") {
    return null
  }

  const reviewPayload = payload as PullRequestReviewCommentEvent
  return {
    file: reviewPayload.comment.path,
    diffHunk: reviewPayload.comment.diff_hunk,
    line: reviewPayload.comment.line,
    originalLine: reviewPayload.comment.original_line,
    position: reviewPayload.comment.position,
    commitId: reviewPayload.comment.commit_id,
    originalCommitId: reviewPayload.comment.original_commit_id,
  }
}

export async function getUserPrompt(opts: {
  isRepoEvent: boolean
  isIssuesEvent: boolean
  isCommentEvent: boolean
  eventName: string
  payload: Payload
  appToken: string
}): Promise<{ userPrompt: string; promptFiles: PromptFile[] }> {
  const { isRepoEvent, isIssuesEvent, isCommentEvent, eventName, payload, appToken } = opts
  const customPrompt = process.env["PROMPT"]
  if (isRepoEvent || isIssuesEvent) {
    if (!customPrompt) {
      const eventType = isRepoEvent ? "scheduled and workflow_dispatch" : "issues"
      throw new Error(`PROMPT input is required for ${eventType} events`)
    }
    return { userPrompt: customPrompt, promptFiles: [] }
  }

  if (customPrompt) {
    return { userPrompt: customPrompt, promptFiles: [] }
  }

  const reviewContext = getReviewCommentContext(eventName, payload)
  const mentions = (process.env["MENTIONS"] || "/saeeol,/kc")
    .split(",")
    .map((m) => m.trim().toLowerCase())
    .filter(Boolean)
  let prompt = (() => {
    if (!isCommentEvent) {
      return "Review this pull request"
    }
    const body = (payload as IssueCommentEvent | PullRequestReviewCommentEvent).comment.body.trim()
    const bodyLower = body.toLowerCase()
    if (mentions.some((m) => bodyLower === m)) {
      if (reviewContext) {
        return `Review this code change and suggest improvements for the commented lines:\n\nFile: ${reviewContext.file}\nLines: ${reviewContext.line}\n\n${reviewContext.diffHunk}`
      }
      return "Summarize this thread"
    }
    if (mentions.some((m) => bodyLower.includes(m))) {
      if (reviewContext) {
        return `${body}\n\nContext: You are reviewing a comment on file "${reviewContext.file}" at line ${reviewContext.line}.\n\nDiff context:\n${reviewContext.diffHunk}`
      }
      return body
    }
    throw new Error(`Comments must mention ${mentions.map((m) => "`" + m + "`").join(" or ")}`)
  })()

  const imgData: PromptFile[] = []

  const mdMatches = prompt.matchAll(/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi)
  const tagMatches = prompt.matchAll(/<img .*?src="(https:\/\/github\.com\/user-attachments\/[^"]+)" \/>/gi)
  const matches = [...mdMatches, ...tagMatches].sort((a, b) => a.index - b.index)
  console.log("Images", JSON.stringify(matches, null, 2))

  let offset = 0
  for (const m of matches) {
    const tag = m[0]
    const url = m[1]
    const start = m.index
    const filename = path.basename(url)

    const res = await fetch(url, {
      headers: {
        Authorization: `Bearer ${appToken}`,
        Accept: "application/vnd.github.v3+json",
      },
    })
    if (!res.ok) {
      console.error(`Failed to download image: ${url}`)
      continue
    }

    const replacement = `@${filename}`
    prompt = prompt.slice(0, start + offset) + replacement + prompt.slice(start + offset + tag.length)
    offset += replacement.length - tag.length

    const contentType = res.headers.get("content-type")
    imgData.push({
      filename,
      mime: contentType?.startsWith("image/") ? contentType : "text/plain",
      content: Buffer.from(await res.arrayBuffer()).toString("base64"),
      start,
      end: start + replacement.length,
      replacement,
    })
  }

  return { userPrompt: prompt, promptFiles: imgData }
}

export function subscribeSessionEvents(sessionId: SessionID) {
  const TOOL: Record<string, [string, string]> = {
    todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
    bash: ["Bash", UI.Style.TEXT_DANGER_BOLD],
    edit: ["Edit", UI.Style.TEXT_SUCCESS_BOLD],
    glob: ["Glob", UI.Style.TEXT_INFO_BOLD],
    grep: ["Grep", UI.Style.TEXT_INFO_BOLD],
    list: ["List", UI.Style.TEXT_INFO_BOLD],
    read: ["Read", UI.Style.TEXT_HIGHLIGHT_BOLD],
    write: ["Write", UI.Style.TEXT_SUCCESS_BOLD],
    websearch: ["Search", UI.Style.TEXT_DIM_BOLD],
  }

  function printEvent(color: string, type: string, title: string) {
    UI.println(
      color + `|`,
      UI.Style.TEXT_NORMAL + UI.Style.TEXT_DIM + ` ${type.padEnd(7, " ")}`,
      "",
      UI.Style.TEXT_NORMAL + title,
    )
  }

  let text = ""
  Bus.subscribe(MessageV2.Event.PartUpdated, (evt) => {
    if (evt.properties.part.sessionID !== sessionId) return
    const part = evt.properties.part

    if (part.type === "tool" && part.state.status === "completed") {
      const [tool, color] = TOOL[part.tool] ?? [part.tool, UI.Style.TEXT_INFO_BOLD]
      const title =
        part.state.title || Object.keys(part.state.input).length > 0
          ? JSON.stringify(part.state.input)
          : "Unknown"
      console.log()
      printEvent(color, tool, title)
    }

    if (part.type === "text") {
      text = part.text

      if (part.time?.end) {
        UI.empty()
        UI.println(UI.markdown(text))
        UI.empty()
        text = ""
        return
      }
    }
  })
}

export async function chat(
  sessionId: SessionID,
  variant: string | undefined,
  providerID: string,
  modelID: string,
  message: string,
  files: PromptFile[],
): Promise<string> {
  console.log("Sending message to saeeol...")

  return AppRuntime.runPromise(
    Effect.gen(function* () {
      const prompt = yield* SessionPrompt.Service
       const result = yield* prompt.prompt({
         sessionID: sessionId,
         messageID: MessageID.ascending(),
         variant,
         model: {
           providerID: ProviderID.make(providerID),
           modelID: ModelID.make(modelID),
        },
        parts: [
          {
            id: PartID.ascending(),
            type: "text",
            text: message,
          },
          ...files.flatMap((f) => [
            {
              id: PartID.ascending(),
              type: "file" as const,
              mime: f.mime,
              url: `data:${f.mime};base64,${f.content}`,
              filename: f.filename,
              source: {
                type: "file" as const,
                text: {
                  value: f.replacement,
                  start: f.start,
                  end: f.end,
                },
                path: f.filename,
              },
            },
          ]),
        ],
      })

      if (result.info.role === "assistant" && result.info.error) {
        const err = result.info.error
        console.error("Agent error:", err)
        if (err.name === "ContextOverflowError") throw new Error(formatPromptTooLargeError(files))
        const msg = "message" in err.data ? err.data.message : ""
        throw new Error(`${err.name}: ${msg}`)
      }

      const responseText = extractResponseText(result.parts)
      if (responseText) return responseText

      console.log("Requesting summary from agent...")
       const summary = yield* prompt.prompt({
         sessionID: sessionId,
         messageID: MessageID.ascending(),
         variant,
         model: {
           providerID: ProviderID.make(providerID),
           modelID: ModelID.make(modelID),
        },
        tools: { "*": false },
        parts: [
          {
            id: PartID.ascending(),
            type: "text",
            text: "Summarize the actions (tool calls & reasoning) you did for the user in 1-2 sentences.",
          },
        ],
      })

      if (summary.info.role === "assistant" && summary.info.error) {
        const err = summary.info.error
        console.error("Summary agent error:", err)
        if (err.name === "ContextOverflowError") throw new Error(formatPromptTooLargeError(files))
        const msg = "message" in err.data ? err.data.message : ""
        throw new Error(`${err.name}: ${msg}`)
      }

      const summaryText = extractResponseText(summary.parts)
      if (!summaryText) throw new Error("Failed to get summary from agent")
      return summaryText
    }),
  )
}

export async function summarize(
  response: string,
  chatFn: (msg: string) => Promise<string>,
  fallbackTitle: string,
): Promise<string> {
  try {
    return await chatFn(`Summarize the following in less than 40 characters:\n\n${response}`)
  } catch {
    return `Fix issue: ${fallbackTitle}`
  }
}
