import { assert, describe, it } from "@effect/vitest"
import { Effect } from "effect"
import * as Reactivity from "effect/unstable/reactivity/Reactivity"
import type * as SqlError from "effect/unstable/sql/SqlError"
import { vi } from "vitest"

const state: {
  connectCause: unknown
} = {
  connectCause: null
}

vi.mock("mysql2", () => ({
  createPool: () => ({
    query: (_sql: string, cb: (cause: unknown) => void) => cb(state.connectCause),
    end: (cb: () => void) => cb(),
    getConnection: (_cb: () => void) => undefined
  })
}))

const connectFailureReason = (cause: unknown) =>
  Effect.gen(function*() {
    state.connectCause = cause
    const { MysqlClient } = yield* Effect.promise(() => import("@effect/sql-mysql2"))
    const error = yield* Effect.flip(MysqlClient.make({}))
    return error.reason
  }).pipe(
    Effect.scoped,
    Effect.provide(Reactivity.layer)
  )

const connectFailureReasonTag = (errno: number) => Effect.map(connectFailureReason({ errno }), (reason) => reason._tag)

const assertUniqueViolation = (reason: SqlError.SqlErrorReason, constraint: string) => {
  assert.strictEqual(reason._tag, "UniqueViolation")
  if (reason._tag === "UniqueViolation") {
    assert.strictEqual(reason.constraint, constraint)
  }
}

describe("MysqlClient SqlError classification", () => {
  it.effect("maps representative errno codes to reasons", () =>
    Effect.gen(function*() {
      const cases = [
        [1040, "ConnectionError"],
        [1045, "AuthenticationError"],
        [1142, "AuthorizationError"],
        [1064, "SqlSyntaxError"],
        [1062, "UniqueViolation"],
        [1048, "ConstraintError"],
        [1213, "DeadlockError"],
        [1205, "LockTimeoutError"],
        [3024, "StatementTimeoutError"]
      ] as const

      for (const [errno, expectedTag] of cases) {
        const tag = yield* connectFailureReasonTag(errno)
        assert.strictEqual(tag, expectedTag)
      }
    }))

  it.effect("falls back to UnknownError for unmapped errno", () =>
    Effect.gen(function*() {
      const tag = yield* connectFailureReasonTag(9999)
      assert.strictEqual(tag, "UnknownError")
    }))

  it.effect("classifies duplicate-entry errno 1062 as UniqueViolation", () =>
    Effect.gen(function*() {
      const reason = yield* connectFailureReason({ errno: 1062 })
      assertUniqueViolation(reason, "unknown")
    }))

  it.effect("extracts duplicate-entry constraints from structured fields and common messages", () =>
    Effect.gen(function*() {
      const explicit = yield* connectFailureReason({
        errno: 1062,
        constraint: "  users_email_key  ",
        sqlMessage: "Duplicate entry 'user@example.com' for key 'ignored_key'"
      })
      assertUniqueViolation(explicit, "users_email_key")

      const singleQuoted = yield* connectFailureReason({
        errno: 1062,
        sqlMessage: "Duplicate entry 'user@example.com' for key 'users_email_key'"
      })
      assertUniqueViolation(singleQuoted, "users_email_key")

      const backtickQuoted = yield* connectFailureReason({
        errno: 1062,
        message: "Duplicate entry '1' for key `PRIMARY`"
      })
      assertUniqueViolation(backtickQuoted, "PRIMARY")

      const unquoted = yield* connectFailureReason({
        errno: 1062,
        sqlMessage: "Duplicate entry 'user@example.com' for key users_email_key"
      })
      assertUniqueViolation(unquoted, "users_email_key")
    }))

  it.effect("uses unknown for blank, missing, malformed, or non-string duplicate-entry metadata", () =>
    Effect.gen(function*() {
      const missing = yield* connectFailureReason({ errno: 1062 })
      assertUniqueViolation(missing, "unknown")

      const blank = yield* connectFailureReason({
        errno: 1062,
        constraint: "   ",
        sqlMessage: "Duplicate entry 'user@example.com' for key '   '"
      })
      assertUniqueViolation(blank, "unknown")

      const malformed = yield* connectFailureReason({
        errno: 1062,
        sqlMessage: "Duplicate entry 'user@example.com'"
      })
      assertUniqueViolation(malformed, "unknown")

      const nonString = yield* connectFailureReason({
        errno: 1062,
        sqlMessage: 1062,
        message: { text: "Duplicate entry 'user@example.com' for key 'users_email_key'" }
      })
      assertUniqueViolation(nonString, "unknown")
    }))

  it.effect("keeps non-unique constraint errno values classified as ConstraintError", () =>
    Effect.gen(function*() {
      const tag = yield* connectFailureReasonTag(1452)
      assert.strictEqual(tag, "ConstraintError")
    }))
})
