# %% A3Sequence ----
test_that("A3Sequence succeeds with valid data", {
  x <- A3Sequence(data = "MKTAYIAKQRQISFVKSHFSRQDILDLWIYHTQGYFPDWQNYTPG")
  expect_s7_class(x, A3Sequence)
})

test_that("A3Sequence fails with invalid data", {
  expect_error(
    A3Sequence(data = c("SRQ", "MKTA")),
    "Sequence must be a single string"
  )
  expect_error(
    A3Sequence(data = "M"),
    "Sequence must be at least 2 characters long"
  )
  expect_error(
    A3Sequence(data = "MK123"),
    "Sequence must only contain uppercase letters"
  )
})


# %% A3Index ----
test_that("A3Index is abstract and cannot be instantiated", {
  expect_error(A3Index(data = c(3L, 5L, 7L)))
})


# %% A3Position ----
test_that("A3Position succeeds with valid data", {
  x <- A3Position(data = c(3L, 5L, 7L))
  expect_s7_class(x, A3Position)
})

test_that("A3Position fails with invalid data", {
  expect_error(
    A3Position(data = c(0L, 5L, 7L)),
    "Position data must be a positive integer."
  )
  expect_error(
    A3Position(data = c(-1L, 5L, 7L)),
    "Position data must be a positive integer."
  )
  expect_error(
    A3Position(data = c(1.5, 5L, 7L)),
    "object properties are invalid"
  )
})


# %% A3Range ----
test_that("A3Range succeeds with valid data", {
  # Single range
  x <- A3Range(data = rbind(c(1L, 5L)))
  expect_s7_class(x, A3Range)
  # Two non-overlapping ranges: [1,5] and [10,15]
  x <- A3Range(data = rbind(c(1L, 5L), c(10L, 15L)))
  expect_s7_class(x, A3Range)
  # Adjacent ranges are permitted: [1,5] and [6,10]
  x <- A3Range(data = rbind(c(1L, 5L), c(6L, 10L)))
  expect_s7_class(x, A3Range)
})

test_that("A3Range fails with invalid data", {
  expect_error(
    A3Range(data = rbind(c(0L, 10L), c(5L, 15L))),
    "Range values must be positive integers."
  )
  expect_error(
    A3Range(data = rbind(c(1.5, 10L), c(5L, 15L))),
    "object properties are invalid"
  )
  expect_error(
    A3Range(data = rbind(c(5L, 1L), c(10L, 15L))),
    "Start of range must be less than end of range."
  )
  # Overlapping ranges: [1,5] and [3,8]
  expect_error(
    A3Range(data = rbind(c(1L, 5L), c(3L, 8L))),
    "must not overlap"
  )
})

# %% A3Feature ----
test_that("A3Feature is abstract and cannot be instantiated", {
  expect_error(A3Feature(type = "phosphorylation"))
})


# %% A3Site ----
test_that("A3Site succeeds with valid index and optional type", {
  x <- A3Site(index = A3Position(data = c(3L, 5L, 7L)))
  expect_s7_class(x, A3Site)
  x <- A3Site(
    index = A3Position(data = c(3L, 5L, 7L)),
    type = "phosphorylation"
  )
  expect_s7_class(x, A3Site)
})

test_that("A3Site fails with invalid index or type", {
  expect_error(
    A3Site(index = A3Position(data = c(0L, 5L, 7L))),
    "Position data must be a positive integer."
  )
  expect_error(
    A3Site(index = A3Position(data = c(1.5, 5L, 7L))),
    "object properties are invalid"
  )
  expect_error(
    A3Site(index = A3Position(data = c(1.5, 5L, 7L)), type = 99L),
    "object properties are invalid"
  )
})


# %% A3Region ----
test_that("A3Region succeeds with valid index and optional type", {
  x <- A3Region(index = A3Range(data = rbind(c(1L, 5L), c(10L, 12L))))
  expect_s7_class(x, A3Region)
  x <- A3Region(
    index = A3Range(data = rbind(c(1L, 5L), c(10L, 12L))),
    type = "Phosphodegron"
  )
  expect_s7_class(x, A3Region)
})

test_that("A3Region fails with invalid index or type", {
  expect_error(
    A3Region(index = A3Range(data = rbind(c(0L, 5L), c(10L, 12L)))),
    "Range values must be positive integers"
  )
  expect_error(
    A3Region(index = A3Range(data = rbind(c(1.5, 5L), c(10L, 12L)))),
    "object properties are invalid"
  )
  expect_error(
    A3Region(index = A3Range(data = rbind(c(5L, 1L), c(10L, 12L)))),
    "Start of range must be less than end of range"
  )
  expect_error(
    A3Region(
      index = A3Range(data = rbind(c(1L, 5L), c(10L, 12L))),
      type = 99L
    ),
    "object properties are invalid"
  )
})


# %% A3PTM ----
test_that("A3PTM succeeds with valid index and optional type", {
  x <- A3PTM(index = A3Position(data = c(3L, 5L, 7L)))
  expect_s7_class(x, A3PTM)
  x <- A3PTM(
    index = A3Range(data = rbind(c(3L, 7L), c(10L, 12L))),
    type = "phosphorylation"
  )
  expect_s7_class(x, A3PTM)
})

test_that("A3PTM fails with invalid index or type", {
  expect_error(
    A3PTM(index = A3Position(data = c(0L, 5L, 7L))),
    "Position data must be a positive integer."
  )
  expect_error(
    A3PTM(index = A3Position(data = c(1.5, 5L, 7L))),
    "object properties are invalid"
  )
  expect_error(
    A3PTM(index = A3Position(data = c(1.5, 5L, 7L)), type = 99L),
    "object properties are invalid"
  )
})

# %% A3Processing ----
test_that("A3Processing succeeds with valid index and optional type", {
  x <- A3Processing(index = A3Position(data = c(3L, 5L, 7L)))
  expect_s7_class(x, A3Processing)
  x <- A3Processing(
    index = A3Range(data = rbind(c(3L, 7L), c(10L, 12L))),
    type = "signal peptide"
  )
  expect_s7_class(x, A3Processing)
})

test_that("A3Processing fails with invalid index or type", {
  expect_error(
    A3Processing(index = A3Position(data = c(0L, 5L, 7L))),
    "Position data must be a positive integer."
  )
  expect_error(
    A3Processing(index = A3Position(data = c(1.5, 5L, 7L))),
    "object properties are invalid"
  )
  expect_error(
    A3Processing(index = A3Position(data = c(1.5, 5L, 7L)), type = 99L),
    "object properties are invalid"
  )
})


# %% A3Variant ----
test_that("A3Variant succeeds with valid position and info", {
  x <- A3Variant(
    position = A3Position(data = c(3L)),
    info = list(field_a = "alpha", field_b = 123L)
  )
  expect_s7_class(x, A3Variant)
})

test_that("A3Variant fails with invalid position or info", {
  expect_error(
    A3Variant(
      position = A3Position(data = c(1L, 3L)),
      info = list(field_a = "alpha")
    ),
    "Variant position must be a single integer."
  )
})

# %% A3Annotation ----
test_that("A3Annotation succeeds with valid annotations", {
  x <- A3Annotation(
    site = list(activeSite = A3Site(index = A3Position(data = c(3L, 5L))))
  )
  expect_s7_class(x, A3Annotation)
  x <- A3Annotation(
    site = list(activeSite = A3Site(index = A3Position(data = c(3L, 5L)))),
    region = list(
      KXGS = A3Region(
        index = A3Range(data = rbind(c(1L, 10L)))
      )
    )
  )
  expect_s7_class(x, A3Annotation)
  x <- A3Annotation(
    site = list(activeSite = A3Site(index = A3Position(data = c(3L, 5L)))),
    region = list(
      KXGS = A3Region(
        index = A3Range(data = rbind(c(1L, 10L)))
      )
    ),
    ptm = list(Phosphorylation = A3PTM(index = A3Position(data = c(7L))))
  )
  expect_s7_class(x, A3Annotation)
  x <- A3Annotation(
    site = list(activeSite = A3Site(index = A3Position(data = c(3L, 5L)))),
    region = list(
      KXGS = A3Region(
        index = A3Range(data = rbind(c(1L, 10L)))
      )
    ),
    ptm = list(Phosphorylation = A3PTM(index = A3Position(data = c(7L)))),
    processing = list(
      `Signal peptide` = A3Processing(
        index = A3Range(data = rbind(c(20L, 30L)))
      )
    )
  )
  expect_s7_class(x, A3Annotation)
  x <- A3Annotation(
    site = list(activeSite = A3Site(index = A3Position(data = c(3L, 5L)))),
    region = list(
      KXGS = A3Region(
        index = A3Range(data = rbind(c(1L, 10L)))
      )
    ),
    ptm = list(Phosphorylation = A3PTM(index = A3Position(data = c(7L)))),
    processing = list(
      `Signal peptide` = A3Processing(
        index = A3Range(data = rbind(c(20L, 30L)))
      )
    ),
    variant = list(A3Variant(
      position = A3Position(data = c(15L)),
      info = list(mutation = "R15H")
    ))
  )
  expect_s7_class(x, A3Annotation)
})

test_that("A3Annotation fails with invalid data", {
  expect_error(
    A3Annotation(
      site = list("not_a_site"),
      region = list(),
      ptm = list(),
      processing = list(),
      variant = list()
    ),
    "All site annotations must be A3Site objects."
  )
  expect_error(
    A3Annotation(
      site = list(),
      region = list("not_a_region"),
      ptm = list(),
      processing = list(),
      variant = list()
    ),
    "All region annotations must be A3Region objects."
  )
  expect_error(
    A3Annotation(
      site = list(),
      region = list(),
      ptm = list("not_a_ptm"),
      processing = list(),
      variant = list()
    ),
    "All PTM annotations must be A3PTM objects."
  )
  expect_error(
    A3Annotation(
      site = list(),
      region = list(),
      ptm = list(),
      processing = list("not_processing"),
      variant = list()
    ),
    "All processing annotations must be A3Processing objects."
  )
  expect_error(
    A3Annotation(
      site = list(),
      region = list(),
      ptm = list(),
      processing = list(),
      variant = list("not_a_variant")
    ),
    "All variant annotations must be A3Variant objects."
  )
  expect_error(
    A3Annotation(
      site = list(A3Site(index = A3Position(data = c(3L, 5L))))
    ),
    "All site annotation names must be non-empty strings."
  )
  expect_error(
    A3Annotation(
      site = setNames(
        list(A3Site(index = A3Position(data = c(3L, 5L)))),
        ""
      )
    ),
    "All site annotation names must be non-empty strings."
  )
})


# %% Metadata ----
test_that("Metadata can be instantiated", {
  x <- Metadata()
  expect_s7_class(x, Metadata)
})


# %% A3Metadata ----
test_that("A3Metadata can be instantiated", {
  x <- A3Metadata()
  expect_s7_class(x, A3Metadata)
  expect_identical(x@uniprot_id, "")
  expect_identical(x@description, "")
  expect_identical(x@reference, "")
  expect_identical(x@organism, "")
})

test_that("A3Metadata fails with invalid data", {
  expect_error(
    A3Metadata(uniprot_id = 123L),
    "object properties are invalid"
  )
  expect_error(
    A3Metadata(description = 123L),
    "object properties are invalid"
  )
  expect_error(
    A3Metadata(reference = 123L),
    "object properties are invalid"
  )
  expect_error(
    A3Metadata(organism = 123L),
    "object properties are invalid"
  )
  expect_error(
    A3Metadata(uniprot_id = c("P10636", "Q9Y3Q8")),
    "uniprot_id.*character\\(1\\)"
  )
  expect_error(
    A3Metadata(organism = c("Homo sapiens", "Mus musculus")),
    "organism.*character\\(1\\)"
  )
})


# %% A3 ----
test_that("A3 can be instantiated with valid sequence and annotations", {
  x <- A3(
    sequence = A3Sequence(data = "MKTAYIAKQRQISFVK"),
    annotations = A3Annotation(
      site = list(activeSite = A3Site(index = A3Position(data = c(3L, 5L)))),
      region = list(
        KXGS = A3Region(
          index = A3Range(data = rbind(c(1L, 10L)))
        )
      ),
      ptm = list(Phosphorylation = A3PTM(index = A3Position(data = c(7L)))),
      processing = list(
        `Signal peptide` = A3Processing(
          index = A3Range(data = rbind(c(8L, 12L)))
        )
      ),
      variant = list(A3Variant(
        position = A3Position(data = c(15L)),
        info = list(mutation = "R15H")
      ))
    )
  )
  expect_s7_class(x, A3)
})

test_that("A3 fails with invalid sequence or annotations", {
  expect_error(
    A3(
      sequence = "not_a_sequence",
      annotations = A3Annotation()
    ),
    "object properties are invalid"
  )
  expect_error(
    A3(
      sequence = A3Sequence(data = "MKTAYIAKQRQISFVK"),
      annotations = "not_annotations"
    ),
    "object properties are invalid"
  )
})


# %% create_A3 ----
test_that("create_A3 succeeds with valid inputs", {
  x <- create_A3(
    sequence = "MKTAYIAKQRQISFVK",
    site = list(
      `N-terminal repeat` = annotation_position(
        c(3, 5)
      )
    ),
    region = list(
      Phosphodegron = annotation_range(
        rbind(c(1, 10)),
        type = "functional region"
      )
    ),
    ptm = list(
      Phosphorylation = annotation_position(
        c(7)
      )
    ),
    processing = list(
      `Signal peptide` = annotation_range(
        rbind(c(8, 12))
      )
    ),
    variant = list(
      zdorg = annotation_variant(
        15,
        info = list(mutation = "R15H")
      )
    ),
    uniprot_id = "P12345",
    description = "Example protein",
    reference = "PMID:12345678",
    organism = "Homo sapiens"
  )
  expect_s7_class(x, A3)
})


# %% to_json / A3from_json ----
test_that("to_json produces valid JSON with canonical structure", {
  x <- create_A3(
    sequence = "MKTAYIAKQRQISFVK",
    site = list(
      `Active site` = annotation_position(c(3, 5), type = "activeSite")
    ),
    region = list(
      KXGS = annotation_range(rbind(c(1L, 10L)))
    ),
    ptm = list(
      Phosphorylation = annotation_position(c(7))
    ),
    processing = list(
      `Signal peptide` = annotation_range(rbind(c(8L, 12L)))
    ),
    variant = list(
      annotation_variant(15, info = list(from = "R", to = "H"))
    ),
    uniprot_id = "P12345",
    description = "Example protein",
    organism = "Homo sapiens"
  )
  json <- to_json(x)
  expect_type(json, "character")
  parsed <- jsonlite::fromJSON(json, simplifyVector = FALSE)
  expect_equal(parsed$sequence, "MKTAYIAKQRQISFVK")
  expect_equal(parsed$annotations$site$`Active site`$type, "activeSite")
  expect_equal(parsed$annotations$region$KXGS$index, list(list(1L, 10L)))
  expect_equal(parsed$metadata$uniprot_id, "P12345")
  expect_equal(parsed$metadata$organism, "Homo sapiens")
  expect_equal(parsed$metadata$reference, "")
})

test_that("A3from_json round-trips to_json with zero loss", {
  original <- create_A3(
    sequence = "MKTAYIAKQRQISFVK",
    site = list(
      `Active site` = annotation_position(c(3, 5), type = "activeSite")
    ),
    region = list(
      KXGS = annotation_range(rbind(c(1L, 10L)))
    ),
    ptm = list(
      Phosphorylation = annotation_position(c(7))
    ),
    processing = list(
      `Signal peptide` = annotation_range(rbind(c(8L, 12L)))
    ),
    variant = list(
      annotation_variant(15, info = list(from = "R", to = "H"))
    ),
    uniprot_id = "P12345",
    description = "Example protein",
    reference = "PMID:12345678",
    organism = "Homo sapiens"
  )

  restored <- A3from_json(to_json(original))

  # Sequence
  expect_identical(restored@sequence@data, original@sequence@data)

  # Site annotations
  expect_identical(
    names(restored@annotations@site),
    names(original@annotations@site)
  )
  expect_identical(
    restored@annotations@site[[1]]@index@data,
    original@annotations@site[[1]]@index@data
  )
  expect_identical(
    restored@annotations@site[[1]]@type,
    original@annotations@site[[1]]@type
  )

  # Region annotations
  expect_identical(
    names(restored@annotations@region),
    names(original@annotations@region)
  )
  expect_identical(
    restored@annotations@region[[1]]@index@data,
    original@annotations@region[[1]]@index@data
  )

  # PTM annotations
  expect_identical(
    restored@annotations@ptm[[1]]@index@data,
    original@annotations@ptm[[1]]@index@data
  )

  # Processing annotations
  expect_identical(
    restored@annotations@processing[[1]]@index@data,
    original@annotations@processing[[1]]@index@data
  )

  # Variant annotations
  expect_identical(
    restored@annotations@variant[[1]]@position@data,
    original@annotations@variant[[1]]@position@data
  )
  expect_identical(
    restored@annotations@variant[[1]]@info$from,
    original@annotations@variant[[1]]@info$from
  )

  # Metadata
  expect_identical(restored@metadata@uniprot_id, original@metadata@uniprot_id)
  expect_identical(restored@metadata@description, original@metadata@description)
  expect_identical(restored@metadata@reference, original@metadata@reference)
  expect_identical(restored@metadata@organism, original@metadata@organism)
})

test_that("A3from_json rejects legacy bare-array format", {
  legacy_json <- '{
    "$schema": "https://schema.rtemis.org/a3/v1/schema.json",
    "a3_version": "1.0.0",
    "sequence": "MKTAYIAKQRQISFVK",
    "annotations": {
      "site": {
        "Active site": [3, 5]
      },
      "region": {},
      "ptm": {},
      "processing": {},
      "variant": []
    },
    "metadata": {}
  }'
  expect_error(A3from_json(legacy_json), "index")
})

test_that("A3from_json rejects missing metadata", {
  json <- '{
    "$schema": "https://schema.rtemis.org/a3/v1/schema.json",
    "a3_version": "1.0.0",
    "sequence": "MKTAYIAKQRQISFVK",
    "annotations": {
      "site": {},
      "region": {},
      "ptm": {},
      "processing": {},
      "variant": []
    }
  }'
  expect_error(A3from_json(json), "metadata")
})

test_that("A3from_json reports duplicate positions with annotation path", {
  json <- '{
    "$schema": "https://schema.rtemis.org/a3/v1/schema.json",
    "a3_version": "1.0.0",
    "sequence": "MKTAYIAKQRQISFVK",
    "annotations": {
      "site": {},
      "region": {},
      "ptm": {
        "Phosphorylation": {
          "index": [7, 9, 7],
          "type": ""
        }
      },
      "processing": {},
      "variant": []
    },
    "metadata": {}
  }'
  expect_error(
    A3from_json(json),
    "annotations\\.ptm\\.Phosphorylation\\.index.*duplicate positions.*7"
  )
})

test_that("A3from_json accepts pre-parsed list", {
  lst <- list(
    `$schema` = "https://schema.rtemis.org/a3/v1/schema.json",
    a3_version = "1.0.0",
    sequence = "MKTAYIAKQRQISFVK",
    annotations = list(
      site = list(
        `Active site` = list(index = c(3L, 5L), type = "activeSite")
      ),
      region = list(),
      ptm = list(),
      processing = list(),
      variant = list()
    ),
    metadata = list(uniprot_id = "P12345")
  )
  x <- A3from_json(lst)
  expect_s7_class(x, A3)
  expect_identical(x@annotations@site$`Active site`@type, "activeSite")
  expect_identical(x@metadata@uniprot_id, "P12345")
})

test_that("read_A3json reports valid A3 version", {
  x <- create_A3(
    sequence = "MKTAYIAKQRQISFVK",
    site = list(
      `Active site` = annotation_position(c(3, 5), type = "activeSite")
    ),
    uniprot_id = "P12345"
  )
  filepath <- tempfile(fileext = ".json")
  write_A3json(x, filepath, overwrite = TRUE)

  expect_message(
    read_A3json(filepath, verbosity = 1L),
    "valid A3 1\\.0\\.0"
  )
})
