//
//  Account.swift
//  Pachyderm
//
//  Created by Shadowfacts on 9/8/18.
//  Copyright © 2018 Shadowfacts. All rights reserved.
//

import Foundation

public final class Account: AccountProtocol, Decodable, Sendable {
    public let id: String
    public let username: String
    public let acct: String
    public let displayName: String
    public let locked: Bool
    public let createdAt: Date
    public let followersCount: Int
    public let followingCount: Int
    public let statusesCount: Int
    public let note: String
    public let url: URL
    // required on mastodon, but optional on gotosocial
    public let avatar: URL?
    public let avatarStatic: URL?
    public let header: URL?
    public let headerStatic: URL?
    public let emojis: [Emoji]
    public let moved: Bool?
    public let movedTo: Account?
    public let fields: [Field]
    public let bot: Bool?
    
    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        self.id = try container.decode(String.self, forKey: .id)
        self.username = try container.decode(String.self, forKey: .username)
        self.acct = try container.decode(String.self, forKey: .acct)
        self.displayName = try container.decode(String.self, forKey: .displayName)
        self.locked = try container.decode(Bool.self, forKey: .locked)
        self.createdAt = try container.decode(Date.self, forKey: .createdAt)
        self.followersCount = try container.decode(Int.self, forKey: .followersCount)
        self.followingCount = try container.decode(Int.self, forKey: .followingCount)
        self.statusesCount = try container.decode(Int.self, forKey: .statusesCount)
        self.note = try container.decode(String.self, forKey: .note)
        self.url = try container.decode(URL.self, forKey: .url)
        self.avatar = try? container.decode(URL.self, forKey: .avatar)
        self.avatarStatic = try? container.decode(URL.self, forKey: .avatarStatic)
        self.header = try? container.decode(URL.self, forKey: .header)
        self.headerStatic = try? container.decode(URL.self, forKey: .headerStatic)
        // even up-to-date pixelfed instances sometimes lack this, for reasons unclear
        if let emojis = try container.decodeIfPresent([Emoji].self, forKey: .emojis) {
            self.emojis = emojis
        } else {
            self.emojis = []
        }
        self.fields = (try? container.decode([Field].self, forKey: .fields)) ?? []
        self.bot = try? container.decode(Bool.self, forKey: .bot)
        
        if let moved = try? container.decode(Bool.self, forKey: .moved) {
            self.moved = moved
            self.movedTo = nil
        } else if let account = try? container.decode(Account.self, forKey: .moved) {
            self.moved = true
            self.movedTo = account
        } else {
            self.moved = false
            self.movedTo = nil
        }
    }
    
    public static func authorizeFollowRequest(_ accountID: String) -> Request<Relationship> {
        return Request<Relationship>(method: .post, path: "/api/v1/follow_requests/\(accountID)/authorize")
    }
    
    public static func rejectFollowRequest(_ accountID: String) -> Request<Relationship> {
        return Request<Relationship>(method: .post, path: "/api/v1/follow_requests/\(accountID)/reject")
    }
    
    public static func removeFromFollowRequests(_ account: Account) -> Request<Empty> {
        return Request<Empty>(method: .delete, path: "/api/v1/suggestions/\(account.id)")
    }
    
    public static func getFollowers(_ accountID: String, range: RequestRange = .default) -> Request<[Account]> {
        var request = Request<[Account]>(method: .get, path: "/api/v1/accounts/\(accountID)/followers")
        request.range = range
        return request
    }
    
    public static func getFollowing(_ accountID: String, range: RequestRange = .default) -> Request<[Account]> {
        var request = Request<[Account]>(method: .get, path: "/api/v1/accounts/\(accountID)/following")
        request.range = range
        return request
    }
    
    public static func getStatuses(_ accountID: String, range: RequestRange = .default, onlyMedia: Bool? = nil, pinned: Bool? = nil, excludeReplies: Bool? = nil, excludeReblogs: Bool? = nil) -> Request<[Status]> {
        var request = Request<[Status]>(method: .get, path: "/api/v1/accounts/\(accountID)/statuses", queryParameters: [
                "only_media" => onlyMedia,
                "pinned" => pinned,
                "exclude_replies" => excludeReplies,
                "exclude_reblogs" => excludeReblogs,
            ])
        request.range = range
        return request
    }
    
    public static func follow(_ accountID: String) -> Request<Relationship> {
        return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/follow")
    }
    
    public static func setShowReblogs(_ accountID: String, showReblogs: Bool) -> Request<Relationship> {
        return Request(method: .post, path: "/api/v1/accounts/\(accountID)/follow", body: ParametersBody([
            "reblogs" => showReblogs
        ]))
    }
    
    public static func unfollow(_ accountID: String) -> Request<Relationship> {
        return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unfollow")
    }
    
    public static func block(_ accountID: String) -> Request<Relationship> {
        return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/block")
    }
    
    public static func unblock(_ accountID: String) -> Request<Relationship> {
        return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unblock")
    }
    
    public static func mute(_ accountID: String, notifications: Bool? = nil) -> Request<Relationship> {
        return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/mute", body: ParametersBody([
                "notifications" => notifications
            ]))
    }
    
    public static func unmute(_ accountID: String) -> Request<Relationship> {
        return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unmute")
    }
    
    public static func getLists(_ account: Account) -> Request<[List]> {
        return Request<[List]>(method: .get, path: "/api/v1/accounts/\(account.id)/lists")
    }
    
    private enum CodingKeys: String, CodingKey {
        case id
        case username
        case acct
        case displayName = "display_name"
        case locked
        case createdAt = "created_at"
        case followersCount = "followers_count"
        case followingCount = "following_count"
        case statusesCount = "statuses_count"
        case note
        case url
        case avatar
        case avatarStatic = "avatar_static"
        case header
        case headerStatic = "header_static"
        case emojis
        case moved
        case fields
        case bot
    }
}

extension Account: CustomDebugStringConvertible {
    public var debugDescription: String {
        return "Account(\(id), \(acct))"
    }
}

extension Account {
    public struct Field: Codable, Equatable, Sendable {
        public let name: String
        public let value: String
        public let verifiedAt: Date?
        
        enum CodingKeys: String, CodingKey {
            case name
            case value
            case verifiedAt = "verified_at"
        }
    }
}
