import path from "path"
import { fileURLToPath } from "url"
import { Schema } from "effect"
import { Global } from "@novaclaw/core/global"

type BaseReference = {
  host: string
  path: string
  segments: string[]
  owner?: string
  repo: string
  remote: string
  label: string
}

export type RemoteReference = BaseReference & {
  protocol?: string
}

export type FileReference = BaseReference & {
  host: "file"
  protocol: "file:"
}

export type Reference = RemoteReference | FileReference

export class InvalidRepositoryReferenceError extends Schema.TaggedErrorClass<InvalidRepositoryReferenceError>()(
  "RepositoryInvalidReferenceError",
  {
    repository: Schema.String,
    message: Schema.String,
  },
) {}

export class UnsupportedLocalRepositoryError extends Schema.TaggedErrorClass<UnsupportedLocalRepositoryError>()(
  "RepositoryUnsupportedLocalRepositoryError",
  {
    repository: Schema.String,
    message: Schema.String,
  },
) {}

export class InvalidRepositoryBranchError extends Schema.TaggedErrorClass<InvalidRepositoryBranchError>()(
  "RepositoryInvalidBranchError",
  {
    branch: Schema.String,
    message: Schema.String,
  },
) {}

export type RepositoryError =
  | InvalidRepositoryReferenceError
  | UnsupportedLocalRepositoryError
  | InvalidRepositoryBranchError

export function isRepositoryError(error: unknown): error is RepositoryError {
  return (
    error instanceof InvalidRepositoryReferenceError ||
    error instanceof UnsupportedLocalRepositoryError ||
    error instanceof InvalidRepositoryBranchError
  )
}

function normalizeRepositoryInput(input: string) {
  return input
    .trim()
    .replace(/^git\+/, "")
    .replace(/#.*$/, "")
    .replace(/\/+$/, "")
}

function trimGitSuffix(input: string) {
  return input.replace(/\.git$/, "")
}

function parts(input: string) {
  return input
    .split("/")
    .map((item) => trimGitSuffix(item.trim()))
    .filter(Boolean)
}

function safeHost(input: string) {
  return Boolean(input) && !input.startsWith("-") && !/[\s/\\]/.test(input)
}

function safeSegment(input: string) {
  return input !== "." && input !== ".." && !input.includes(":") && !/[\s/\\]/.test(input)
}

function hostLike(input: string) {
  return input.includes(".") || input.includes(":") || input === "localhost"
}

function withSlash(input: string) {
  return input.endsWith("/") ? input : `${input}/`
}

function githubRemote(pathname: string) {
  const base = process.env.NOVACLAW_REPO_CLONE_GITHUB_BASE_URL
  if (!base) return `https://github.com/${pathname}.git`
  return new URL(`${pathname}.git`, withSlash(base)).href
}

function buildRemoteReference(input: { host: string; segments: string[]; remote?: string; protocol?: string }) {
  const segments = input.segments.map(trimGitSuffix).filter(Boolean)
  if (!safeHost(input.host) || !segments.length || segments.some((segment) => !safeSegment(segment))) return null
  const pathname = segments.join("/")
  const repo = segments[segments.length - 1]
  const host = input.host.toLowerCase()
  return {
    host,
    path: pathname,
    segments,
    owner: segments.length === 2 ? segments[0] : undefined,
    repo,
    remote: input.remote ?? (host === "github.com" ? githubRemote(pathname) : `https://${host}/${pathname}.git`),
    label: host === "github.com" && segments.length === 2 ? pathname : `${host}/${pathname}`,
    protocol: input.protocol,
  } satisfies RemoteReference
}

function buildFileReference(input: { url: URL; remote: string }) {
  const filePath = path.normalize(fileURLToPath(input.url))
  const segments = filePath.split(/[\\/]+/).filter(Boolean)
  if (!segments.length) return null
  return {
    host: "file",
    path: filePath,
    segments: segments.map((segment) => segment.replace(/:$/, "")),
    owner: undefined,
    repo: trimGitSuffix(segments[segments.length - 1]),
    remote: input.remote,
    label: filePath,
    protocol: "file:",
  } satisfies FileReference
}

export function parseRepositoryReference(input: string) {
  const cleaned = normalizeRepositoryInput(input)
  if (!cleaned) return null

  const githubPrefixed = cleaned.match(/^github:([^/\s]+)\/([^/\s]+)$/)
  if (githubPrefixed) {
    return buildRemoteReference({ host: "github.com", segments: [githubPrefixed[1], githubPrefixed[2]] })
  }

  if (!cleaned.includes("://")) {
    const scp = cleaned.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/)
    if (scp) return buildRemoteReference({ host: scp[1], segments: parts(scp[2]), remote: cleaned })

    const direct = parts(cleaned)
    if (direct.length >= 2 && hostLike(direct[0])) {
      return buildRemoteReference({ host: direct[0], segments: direct.slice(1) })
    }

    if (direct.length === 2) {
      return buildRemoteReference({ host: "github.com", segments: direct })
    }
  }

  try {
    const url = new URL(cleaned)
    if (url.protocol === "file:") return buildFileReference({ url, remote: cleaned })
    const pathname = parts(url.pathname)
    const host = url.host
    return buildRemoteReference({
      host,
      segments: pathname,
      remote: host === "github.com" ? githubRemote(pathname.join("/")) : cleaned,
      protocol: url.protocol,
    })
  } catch {
    return null
  }
}

export function isFileRepositoryReference(reference: Reference): reference is FileReference {
  return reference.protocol === "file:"
}

export function isRemoteRepositoryReference(reference: Reference): reference is RemoteReference {
  return !isFileRepositoryReference(reference)
}

export function parseRemoteRepositoryReference(input: string) {
  const reference = parseRepositoryReference(input)
  if (!reference) {
    throw new InvalidRepositoryReferenceError({
      repository: input,
      message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
    })
  }
  if (!isRemoteRepositoryReference(reference)) {
    throw new UnsupportedLocalRepositoryError({
      repository: input,
      message: "Local file repositories are not supported",
    })
  }
  return reference
}

export function validateRepositoryBranch(branch: string) {
  if (!/^[A-Za-z0-9/_.-]+$/.test(branch) || branch.startsWith("-") || branch.includes("..")) {
    throw new InvalidRepositoryBranchError({
      branch,
      message:
        "Branch must contain only alphanumeric characters, /, _, ., and -, and cannot start with - or contain ..",
    })
  }
}

export function parseGitHubRemote(input: string) {
  const cleaned = normalizeRepositoryInput(input)
  if (!cleaned.includes("://") && !cleaned.match(/^(?:[^@/\s]+@)?github\.com:/)) return null

  const parsed = parseRepositoryReference(cleaned)
  if (!parsed || parsed.host !== "github.com" || !parsed.owner || parsed.segments.length !== 2) return null
  return { owner: parsed.owner, repo: parsed.repo }
}

export function repositoryCachePath(input: Reference) {
  return path.join(Global.Path.repos, ...input.host.split(":"), ...input.segments)
}

export function repositoryCacheIdentity(input: Reference) {
  return `${input.host}/${input.path}`
}

export function sameRepositoryReference(left: Reference, right: Reference) {
  return repositoryCacheIdentity(left) === repositoryCacheIdentity(right)
}
