import FluentMySQL
import Vapor


// MARK: Models

final class User: MySQLModel {
    var id: Int?
    var active: Bool

    var courses: Siblings<User, Course, UserCoursePivot> {
        return siblings()
    }
}

final class UserCoursePivot: MySQLPivot {
    typealias Left = User
    typealias Right = Course

    static var leftIDKey: WritableKeyPath<UserCoursePivot, Int> = \.userId
    static var rightIDKey: WritableKeyPath<UserCoursePivot, Int> = \.courseId

    var userId: Int
    var courseId: Int

    var id: Int?
}

final class CourseLecturePivot: MySQLPivot {
    typealias Left = Course
    typealias Right = Lecture

    static var leftIDKey: WritableKeyPath<CourseLecturePivot, Int> = \.courseId
    static var rightIDKey: WritableKeyPath<CourseLecturePivot, Int> = \.lectureId

    var courseId: Int
    var lectureId: Int

    var id: Int?
}


final class Course: MySQLModel {
    var id: Int?

    var lectures: Siblings<Course, Lecture, CourseLecturePivot> {
        return siblings()
    }
}

final class Lecture: MySQLModel {
    var id: Int?
}


// MARK: "Controllers"

struct APIUserController {
    // just 1 repository per controller, easy to mock and test
    let repository: APIUserRepository

    // ... actual endpoints using the repository come here
}

struct APICourseController {
    let repository: APICourseRepository

    // ... actual endpoints using the repository come here
}


// MARK: Repository Protocols

protocol APIUserRepository: UserCoursesRepository {
    func users() -> Future<[User]>
    func activeUsers() -> Future<[User]>
}

protocol APICourseRepository: UserCoursesRepository {
    func deleteCourseWithLectures(_ course: Course) -> Future<Void>
}

// An example of common functionality for multiple repositories. Combined using protocol composition.
protocol UserCoursesRepository {
    func courses(for user: User) -> Future<[Course]>
}


// MARK: Concrete Repository

// the actual database repository consist of mostly generic building blocks.
struct MySQLRepository {
    let pool: DatabaseConnectionPool<ConfiguredDatabase<MySQLDatabase>>

    func all<M: MySQLModel>(
        on connection: MySQLConnection,
        modifyQuery: (QueryBuilder<MySQLDatabase, M>) -> Void = { _ in }
    ) -> Future<[M]> {
        let query = M.query(on: connection)
        modifyQuery(query)
        return query.all()
    }

    func first<M: MySQLModel>(
        on connection: MySQLConnection
    ) -> Future<M?> {
        return M.query(on: connection).first()
    }
}


// MARK: Repository conformances

// the protocol implementations for the repository use the building blocks to provide the specific
// functionality that the controllers need.
extension MySQLRepository: APIUserRepository {
    func users() -> Future<[User]> {
        return pool.withConnection { self.all(on: $0) }
    }

    func activeUsers() -> Future<[User]> {
        return pool.withConnection {
            self.all(on: $0) {
                $0.filter(\.active == true)
            }
        }
    }
}

extension MySQLRepository: APICourseRepository {

    // this is an example of a "complex" action where we use a transaction to make sure all can be rolled back
    func deleteCourseWithLectures(_ course: Course) -> Future<Void> {
        pool.withConnection {
            $0.transaction(on: .mysql) { transaction in
                try course
                    .lectures
                    .query(on: transaction)
                    .delete()
                    .flatMap {
                        course.delete(on: transaction)
                    }
            }
        }
    }
}

extension MySQLRepository: UserCoursesRepository {
    func courses(for user: User) -> Future<[Course]> {
        return pool.withConnection {
            try user.courses.query(on: $0).all()
        }
    }
}