package com.jacoby6000.smithplates.sql.service

import com.jacoby6000.smithplates.sql.*
import com.jacoby6000.smithplates.sql.model.*
import software.amazon.smithy.model.Model

class SqlServiceExtractorSpec extends munit.FunSuite {
  test("extracts operations with input, output, and errors") {
    val schema = SqlModelExtractor.extractOrThrow(
      SqlServiceExtractorSpec.internal.assembleServiceModel(
        """
          |use smithplates.codegen.sql#sqlService
          |use smithy.api#error
          |""".stripMargin,
        """
          |@error("client")
          |structure ItemNotFound {
          |    @required
          |    message: String
          |}
          |
          |structure GetItemInput {
          |    @required
          |    id: String
          |}
          |
          |structure GetItemOutput {
          |    @required
          |    name: String
          |}
          |
          |operation GetItem {
          |    input: GetItemInput
          |    output: GetItemOutput
          |    errors: [ItemNotFound]
          |}
          |
          |operation ListItems {
          |    input: Unit
          |    output: GetItemOutput
          |}
          |
          |@sqlService
          |service ItemRepository {
          |    version: "1"
          |    operations: [GetItem, ListItems]
          |}
          |""".stripMargin
      )
    )

    assertEquals(schema.services.size, 1)
    val service = schema.services.head
    assertEquals(service.shapeId.toString, "example#ItemRepository")
    assertEquals(service.version, "1")
    assertEquals(service.operations.map(_.name), List("GetItem", "ListItems"))

    val getItem = service.operations.head
    assertEquals(getItem.inputShape.toString, "example#GetItemInput")
    assertEquals(getItem.outputShape.map(_.toString), Some("example#GetItemOutput"))
    assertEquals(getItem.errorShapes.map(_.toString), List("example#ItemNotFound"))

    val listItems = service.operations(1)
    assertEquals(listItems.inputShape.toString, "smithy.api#Unit")
    assertEquals(listItems.errorShapes, Nil)
  }

  test("fails when no operations are declared") {
    val result = SqlModelExtractor.extract(
      SqlServiceExtractorSpec.internal.assembleServiceModel(
        """
          |use smithplates.codegen.sql#sqlService
          |""".stripMargin,
        """
          |@sqlService
          |service EmptyRepository {
          |    version: "1"
          |    operations: []
          |}
          |""".stripMargin
      )
    )

    assert(result.isInvalid)
    assert(result.swap.toOption.get.exists(_.isInstanceOf[EmptySqlService]))
  }

  test("fails when resources are declared") {
    val result = SqlModelExtractor.extract(
      SqlServiceExtractorSpec.internal.assembleServiceModel(
        """
          |use smithplates.codegen.sql#sqlService
          |""".stripMargin,
        """
          |resource ItemResource {
          |    identifiers: {id: String}
          |    read: GetItem
          |}
          |
          |operation GetItem {
          |    input: Unit
          |    output: Unit
          |}
          |
          |@sqlService
          |service ItemRepository {
          |    version: "1"
          |    resources: [ItemResource]
          |}
          |""".stripMargin
      )
    )

    assert(result.isInvalid)
    assert(result.swap.toOption.get.exists(_.isInstanceOf[InvalidSqlService]))
  }

  test("ignores services without @sqlService") {
    val schema = SqlModelExtractor.extractOrThrow(
      SqlServiceExtractorSpec.internal.assembleServiceModel(
        "",
        """
          |operation Ping {
          |    input: Unit
          |    output: Unit
          |}
          |
          |service PlainService {
          |    version: "1"
          |    operations: [Ping]
          |}
          |""".stripMargin
      )
    )

    assertEquals(schema.services, Nil)
  }
}
object SqlServiceExtractorSpec {

  /** Internal implementation surface — not part of the stable API; subject to change without notice. */
  object internal {
    val tableUses                                                 =
      """
      |use smithplates.codegen.sql#sqlPrimaryKey
      |use smithplates.codegen.sql#sqlTable
      |""".stripMargin
    val minimalTableStructure                                     =
      """
      |@sqlTable(name: "items")
      |structure Item {
      |    @sqlPrimaryKey
      |    id: String
      |}
      |""".stripMargin
    def assembleServiceModel(uses: String, shapes: String): Model =
      SqlTestModelBuilder.assemble(
        s"""$tableUses
         |$uses
         |
         |$minimalTableStructure
         |$shapes
         |""".stripMargin
      )
  }
}
