import Foundation

enum RestAPIError: Error, LocalizedError {
    case invalidURL(String)
    case badStatus(Int, String?)
    case decoding(Error)

    var errorDescription: String? {
        switch self {
        case .invalidURL(let s): return "Ungültige URL: \(s)"
        case .badStatus(let code, let body): return "HTTP \(code): \(body ?? "")"
        case .decoding(let e): return "JSON: \(e.localizedDescription)"
        }
    }
}

/// REST-Schicht analog `RestApi.kt`.
final class RestAPIClient: @unchecked Sendable {
    private let baseURLString: String
    private let session: URLSession
    private let decoder: JSONDecoder
    private let encoder: JSONEncoder

    init(baseURLString: String, session: URLSession) {
        self.baseURLString = baseURLString.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
        self.session = session
        self.decoder = JSONDecoder()
        self.encoder = JSONEncoder()
    }

    func sessionStatus() async throws -> SessionResponse {
        try await request(path: "api/session", method: "GET", body: nil)
    }

    func logout() async throws -> LogoutResponse {
        let url = try url(for: "api/logout")
        var req = URLRequest(url: url)
        req.httpMethod = "POST"
        req.setValue("application/json", forHTTPHeaderField: "Accept")

        let (data, response) = try await session.data(for: req)
        guard let http = response as? HTTPURLResponse else {
            throw RestAPIError.badStatus(-1, nil)
        }
        guard (200 ... 299).contains(http.statusCode) else {
            let text = String(data: data, encoding: .utf8)
            throw RestAPIError.badStatus(http.statusCode, text)
        }
        if data.isEmpty {
            return LogoutResponse(success: true)
        }
        do {
            return try decoder.decode(LogoutResponse.self, from: data)
        } catch {
            throw RestAPIError.decoding(error)
        }
    }

    func countries() async throws -> [String: String] {
        try await request(path: "api/countries", method: "GET", body: nil)
    }

    func feedback() async throws -> FeedbackResponse {
        try await request(path: "api/feedback", method: "GET", body: nil)
    }

    func feedbackAdminStatus() async throws -> FeedbackAdminStatusResponse {
        try await request(path: "api/feedback/admin-status", method: "GET", body: nil)
    }

    func submitFeedback(_ requestBody: FeedbackRequest) async throws {
        let data = try encoder.encode(requestBody)
        try await requestVoid(path: "api/feedback", method: "POST", body: data)
    }

    func feedbackAdminLogin(_ requestBody: FeedbackAdminLoginRequest) async throws -> FeedbackAdminStatusResponse {
        let data = try encoder.encode(requestBody)
        return try await request(path: "api/feedback/admin-login", method: "POST", body: data)
    }

    func feedbackAdminLogout() async throws {
        try await requestVoid(path: "api/feedback/admin-logout", method: "POST", body: Data())
    }

    func deleteFeedback(id: String) async throws {
        let encoded = id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? id
        try await requestVoid(path: "api/feedback/\(encoded)", method: "DELETE", body: nil)
    }

    func partners() async throws -> [PartnerLinkDto] {
        try await request(path: "api/partners", method: "GET", body: nil)
    }

    /// Multipart-Feld `image` wie OkHttp `MultipartBody.Part`.
    func uploadImage(data: Data, fileName: String, mimeType: String) async throws -> (response: ImageUploadResponse, httpStatus: Int) {
        let url = try url(for: "api/upload-image")
        let boundary = "Boundary-\(UUID().uuidString)"
        var req = URLRequest(url: url)
        req.httpMethod = "POST"
        req.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

        var body = Data()
        body.append("--\(boundary)\r\n".data(using: .utf8)!)
        body.append(
            "Content-Disposition: form-data; name=\"image\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!
        )
        body.append("Content-Type: \(mimeType)\r\n\r\n".data(using: .utf8)!)
        body.append(data)
        body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
        req.httpBody = body

        let (respData, response) = try await session.data(for: req)
        guard let http = response as? HTTPURLResponse else {
            throw RestAPIError.badStatus(-1, nil)
        }
        guard (200 ... 299).contains(http.statusCode) else {
            let text = String(data: respData, encoding: .utf8)
            throw RestAPIError.badStatus(http.statusCode, text)
        }
        do {
            let decoded = try decoder.decode(ImageUploadResponse.self, from: respData)
            return (decoded, http.statusCode)
        } catch {
            throw RestAPIError.decoding(error)
        }
    }

    // MARK: - Request

    private func url(for path: String) throws -> URL {
        let trimmed = path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
        guard let url = URL(string: "\(baseURLString)/\(trimmed)") else {
            throw RestAPIError.invalidURL("\(baseURLString)/\(trimmed)")
        }
        return url
    }

    private func requestVoid(path: String, method: String, body: Data?) async throws {
        let url = try url(for: path)
        var req = URLRequest(url: url)
        req.httpMethod = method
        req.httpBody = body
        if let body, !body.isEmpty {
            req.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        req.setValue("application/json", forHTTPHeaderField: "Accept")

        let (data, response) = try await session.data(for: req)
        guard let http = response as? HTTPURLResponse else {
            throw RestAPIError.badStatus(-1, nil)
        }
        guard (200 ... 299).contains(http.statusCode) else {
            let text = String(data: data, encoding: .utf8)
            throw RestAPIError.badStatus(http.statusCode, text)
        }
    }

    private func request<T: Decodable>(path: String, method: String, body: Data?) async throws -> T {
        let url = try url(for: path)
        var req = URLRequest(url: url)
        req.httpMethod = method
        req.httpBody = body
        if let body, !body.isEmpty {
            req.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        req.setValue("application/json", forHTTPHeaderField: "Accept")

        let (data, response) = try await session.data(for: req)
        guard let http = response as? HTTPURLResponse else {
            throw RestAPIError.badStatus(-1, nil)
        }
        guard (200 ... 299).contains(http.statusCode) else {
            let text = String(data: data, encoding: .utf8)
            throw RestAPIError.badStatus(http.statusCode, text)
        }
        do {
            return try decoder.decode(T.self, from: data)
        } catch {
            throw RestAPIError.decoding(error)
        }
    }
}
