package repository

import io.getquill.*
import io.getquill.jdbczio.Quill
import model.User
import zio.*

import java.time.Instant

/** User repository trait defining CRUD operations
  *
  * All methods return typed IO with RepositoryError for proper error handling.
  * Implementations use Quill for compile-time SQL validation.
  *
  * Usage:
  * {{{
  * ZIO.serviceWithZIO[UserRepository](_.findById(1L))
  *   .provide(UserRepository.live, QuillContext.quillLive)
  * }}}
  */
trait UserRepository:
  /** Find user by ID
    *
    * @param id
    *   User ID
    * @return
    *   Some(user) if found, None if not found
    */
  def findById(id: Long): IO[RepositoryError, Option[User]]

  /** Find user by email address
    *
    * @param email
    *   Email to search for (case-sensitive)
    * @return
    *   Some(user) if found, None if not found
    */
  def findByEmail(email: String): IO[RepositoryError, Option[User]]

  /** Create new user
    *
    * @param email
    *   Unique email address
    * @param passwordHash
    *   Pre-hashed password (use bcrypt)
    * @return
    *   Created user with generated ID
    * @throws RepositoryError.Conflict
    *   if email already exists
    */
  def create(email: String, passwordHash: String): IO[RepositoryError, User]

  /** Update existing user
    *
    * @param user
    *   User with updated fields (ID must exist)
    * @return
    *   Updated user
    * @throws RepositoryError.NotFound
    *   if user doesn't exist
    */
  def update(user: User): IO[RepositoryError, User]

  /** Delete user by ID
    *
    * @param id
    *   User ID to delete
    * @return
    *   Unit on success
    * @throws RepositoryError.NotFound
    *   if user doesn't exist
    */
  def delete(id: Long): IO[RepositoryError, Unit]

object UserRepository:
  /** Live implementation layer requiring Quill.Postgres
    *
    * Provides UserRepository backed by PostgreSQL via Quill.
    */
  val live: ZLayer[Quill.Postgres[SnakeCase], Nothing, UserRepository] =
    ZLayer.fromFunction(UserRepositoryLive(_))

  // Accessor methods for ZIO service pattern
  def findById(id: Long): ZIO[UserRepository, RepositoryError, Option[User]] =
    ZIO.serviceWithZIO(_.findById(id))

  def findByEmail(
      email: String
  ): ZIO[UserRepository, RepositoryError, Option[User]] =
    ZIO.serviceWithZIO(_.findByEmail(email))

  def create(
      email: String,
      passwordHash: String
  ): ZIO[UserRepository, RepositoryError, User] =
    ZIO.serviceWithZIO(_.create(email, passwordHash))

  def update(user: User): ZIO[UserRepository, RepositoryError, User] =
    ZIO.serviceWithZIO(_.update(user))

  def delete(id: Long): ZIO[UserRepository, RepositoryError, Unit] =
    ZIO.serviceWithZIO(_.delete(id))

/** Live UserRepository implementation using Quill
  *
  * Provides compile-time validated SQL queries against PostgreSQL. Handles SQL
  * exceptions and converts them to typed RepositoryError values.
  *
  * Table mapping: User -> summer_users (snake_case columns)
  *
  * @param quill
  *   Quill PostgreSQL context with SnakeCase naming
  */
class UserRepositoryLive(quill: Quill.Postgres[SnakeCase])
    extends UserRepository:
  import quill.*

  /** Table schema mapping to summer_users table */
  inline def users = quote(querySchema[User]("summer_users"))

  override def findById(id: Long): IO[RepositoryError, Option[User]] =
    run(users.filter(_.id == lift(id)))
      .map(_.headOption)
      .refineOrDie { case e: java.sql.SQLException =>
        RepositoryError.DatabaseError(e.getMessage, Some(e))
      }

  override def findByEmail(email: String): IO[RepositoryError, Option[User]] =
    run(users.filter(_.email == lift(email)))
      .map(_.headOption)
      .refineOrDie { case e: java.sql.SQLException =>
        RepositoryError.DatabaseError(e.getMessage, Some(e))
      }

  override def create(
      email: String,
      passwordHash: String
  ): IO[RepositoryError, User] =
    val now = Instant.now()
    val newUser = User(0, email, passwordHash, now, now)
    run(users.insertValue(lift(newUser)).returningGenerated(_.id))
      .map(generatedId => newUser.copy(id = generatedId))
      .refineOrDie {
        case e: java.sql.SQLException if e.getSQLState == "23505" =>
          RepositoryError.Conflict("User", s"Email $email already exists")
        case e: java.sql.SQLException =>
          RepositoryError.DatabaseError(e.getMessage, Some(e))
      }

  override def update(user: User): IO[RepositoryError, User] =
    val updatedUser = user.copy(updatedAt = Instant.now())
    run(
      users
        .filter(_.id == lift(user.id))
        .updateValue(lift(updatedUser))
    ).refineOrDie { case e: java.sql.SQLException =>
      RepositoryError.DatabaseError(e.getMessage, Some(e))
    }.flatMap { rowsUpdated =>
      if rowsUpdated == 0 then
        ZIO.fail(RepositoryError.NotFound("User", user.id))
      else ZIO.succeed(updatedUser)
    }

  override def delete(id: Long): IO[RepositoryError, Unit] =
    run(users.filter(_.id == lift(id)).delete)
      .refineOrDie { case e: java.sql.SQLException =>
        RepositoryError.DatabaseError(e.getMessage, Some(e))
      }
      .flatMap { rowsDeleted =>
        if rowsDeleted == 0 then ZIO.fail(RepositoryError.NotFound("User", id))
        else ZIO.unit
      }
