package com.andr3h3nriqu3s.applications

import java.sql.ResultSet
import java.util.UUID
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ResponseStatusException

data class UserStatusNode(
        var id: String,
        var user_id: String,
        var name: String,
        var icon: String,
        var x: Int,
        var y: Int,
        var width: Int,
        var height: Int,
        var permission: Int,
        var visible: Boolean,
        var endable: Boolean,
) {
    companion object : RowMapper<UserStatusNode> {
        override public fun mapRow(rs: ResultSet, rowNum: Int): UserStatusNode {
            return UserStatusNode(
                    rs.getString("id"),
                    rs.getString("user_id"),
                    rs.getString("name"),
                    rs.getString("icon"),
                    rs.getInt("x"),
                    rs.getInt("y"),
                    rs.getInt("width"),
                    rs.getInt("height"),
                    rs.getInt("permission"),
                    rs.getBoolean("visible"),
                    rs.getBoolean("endable"),
            )
        }
    }
}

data class UserStatusLink(
        var id: String,
        var user_id: String,
        var source_node: String?,
        var target_node: String?,
        var bi: Boolean,
        var source_x: Int,
        var source_y: Int,
        var target_x: Int,
        var target_y: Int,
) {
    companion object : RowMapper<UserStatusLink> {
        override public fun mapRow(rs: ResultSet, rowNum: Int): UserStatusLink {
            return UserStatusLink(
                    rs.getString("id"),
                    rs.getString("user_id"),
                    rs.getString("source_node"),
                    rs.getString("target_node"),
                    rs.getBoolean("bi"),
                    rs.getInt("source_x"),
                    rs.getInt("source_y"),
                    rs.getInt("target_x"),
                    rs.getInt("target_y"),
            )
        }
    }
}

@RestController
@ControllerAdvice
@RequestMapping("/api/user/status")
class UserApplicationStatusController(
        val sessionService: SessionService,
        val userStatusNodeService: UserStatusNodeService,
        val userStatusLinkService: UserStatusLinkService,
        val applicationService: ApplicationService,
) {

    //
    // Nodes
    //
    @GetMapping(path = ["/node"], produces = [MediaType.APPLICATION_JSON_VALUE])
    public fun listAll(@RequestHeader("token") token: String): List<UserStatusNode> {
        val user = sessionService.verifyTokenThrow(token)

        return userStatusNodeService.findAllByUserId(user.id)
    }

    @PostMapping(path = ["/node"], produces = [MediaType.APPLICATION_JSON_VALUE])
    public fun create(
            @RequestBody node: UserStatusNode,
            @RequestHeader("token") token: String
    ): UserStatusNode {
        val user = sessionService.verifyTokenThrow(token)
        node.user_id = user.id

        return userStatusNodeService.create(node)
    }

    @PutMapping(path = ["/node"], produces = [MediaType.APPLICATION_JSON_VALUE])
    public fun put(
            @RequestBody node: UserStatusNode,
            @RequestHeader("token") token: String
    ): UserStatusNode? {
        val user = sessionService.verifyTokenThrow(token)

        if (userStatusNodeService.findById(node.id, user) == null) {
            throw NotFound()
        }

        return userStatusNodeService.update(node)
    }

    @DeleteMapping(path = ["/node/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
    public fun delete(@PathVariable id: String, @RequestHeader("token") token: String): Boolean {
        val user = sessionService.verifyTokenThrow(token)

        val node = userStatusNodeService.findById(id, user)

        if (node == null) {
            throw ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find a node")
        }

        val applications = applicationService.findAllByUserStatusId(id, user)

        if (applications.size > 0) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Applications exist with this user status!")
        }

        userStatusLinkService.deleteByNode(user, node)
        userStatusNodeService.delete(node, user)

        return true
    }

    //
    // Link
    //
    @GetMapping(path = ["/link"], produces = [MediaType.APPLICATION_JSON_VALUE])
    public fun listAllLinks(@RequestHeader("token") token: String): List<UserStatusLink> {
        val user = sessionService.verifyTokenThrow(token)
        return userStatusLinkService.findAllByUserId(user)
    }

    @PutMapping(path = ["/link"], produces = [MediaType.APPLICATION_JSON_VALUE])
    public fun createLink(
            @RequestBody link: UserStatusLink,
            @RequestHeader("token") token: String
    ): UserStatusLink {
        val user = sessionService.verifyTokenThrow(token)

        if (link.source_node == link.target_node) {
            throw ResponseStatusException(
                    HttpStatus.BAD_REQUEST,
                    "Request Node and Target Not can not be the same"
            )
        }

        if (link.source_node != null &&
                        userStatusNodeService.findById(link.source_node!!, user) == null
        ) {
            throw ResponseStatusException(HttpStatus.NOT_FOUND, "Request source not found")
        }

        if (link.target_node != null &&
                        userStatusNodeService.findById(link.target_node!!, user) == null
        ) {
            throw ResponseStatusException(HttpStatus.NOT_FOUND, "Request target not found")
        }

        link.user_id = user.id

        return userStatusLinkService.create(link)
    }
}

@Service
public class UserStatusNodeService(val db: JdbcTemplate) {

    public fun findById(id: String, user: UserDb): UserStatusNode? {
        var nodes =
                db.query(
                        "select * from user_status_node where id=? and user_id=?",
                        arrayOf(id, user.id),
                        UserStatusNode
                )
        if (nodes.size == 0) {
            return null
        }

        return nodes[0]
    }

    public fun findAllByUserId(user_id: String): List<UserStatusNode> {
        return db.query(
                        "select * from user_status_node where user_id=?",
                        arrayOf(user_id),
                        UserStatusNode
                )
                .toList()
    }

    public fun update(node: UserStatusNode): UserStatusNode {
        db.update(
                "update user_status_node set name=?, icon=?, x=?, y=?, width=?, height=?, permission=?, visible=?, endable=? where id=?;",
                node.name,
                node.icon,
                node.x,
                node.y,
                node.width,
                node.height,
                node.permission,
                node.visible,
                node.endable,
                node.id,
        )

        return node
    }

    public fun create(node: UserStatusNode): UserStatusNode {
        val id = UUID.randomUUID().toString()
        node.id = id

        db.update(
                "insert into user_status_node (id, user_id, name, icon, x, y, width, height, permission, visible, endable) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
                node.id,
                node.user_id,
                node.name,
                node.icon,
                node.x,
                node.y,
                node.width,
                node.height,
                node.permission,
                node.visible,
                node.endable,
        )

        return node
    }

    public fun delete(node: UserStatusNode, user: UserDb) {
        db.update("delete from user_status_node where id=? and user_id=?;", node.id, user.id)
    }
}

@Service
public class UserStatusLinkService(val db: JdbcTemplate) {

    public fun findById(id: String, user: UserDb): UserStatusLink? {
        val links =
                db.query(
                        "select * from user_status_link where user_id=? and id=?;",
                        arrayOf(id, user.id),
                        UserStatusLink
                )
        if (links.size == 0) {
            return null
        }
        return links[0]
    }

    public fun findAllByUserId(user: UserDb): List<UserStatusLink> {
        return db.query(
                        "select * from user_status_link where user_id=?",
                        arrayOf(user.id),
                        UserStatusLink
                )
                .toList()
    }

    public fun deleteByNode(user: UserDb, node: UserStatusNode) {
        db.update("delete from user_status_link where (target_node=? or source_node=?) and user_id=?;", node.id, node.id, user.id)
    }

    public fun create(link: UserStatusLink): UserStatusLink {
        val id = UUID.randomUUID().toString()
        link.id = id

        db.update(
                "insert into user_status_link (id, user_id, source_node, target_node, bi, source_x, source_y, target_x, target_y) values (?, ?, ?, ?, ?, ?, ?, ?, ?);",
                link.id,
                link.user_id,
                link.source_node,
                link.target_node,
                link.bi,
                link.source_x,
                link.source_y,
                link.target_x,
                link.target_y,
        )

        return link
    }
}
