import Foundation
import UIKit

enum ProfileServiceError: LocalizedError {
    case unexpectedStatus(String)
    case decoding(debugDescription: String)
    case encoding(String)

    var errorDescription: String? {
        switch self {
        case .unexpectedStatus(let message):
            return message
        case .decoding(let debugDescription):
            return AppConfig.DEBUG
                ? debugDescription
                : NSLocalizedString("Не удалось загрузить профиль.", comment: "Profile service decoding error")
        case .encoding(let message):
            return message
        }
    }
}

final class ProfileService {
    private let client: NetworkClient
    private let decoder: JSONDecoder

    init(client: NetworkClient = .shared) {
        self.client = client
        self.decoder = JSONDecoder()
        self.decoder.keyDecodingStrategy = .convertFromSnakeCase
        self.decoder.dateDecodingStrategy = .custom(Self.decodeDate)
    }

    func fetchMyProfile(completion: @escaping (Result<ProfileDataPayload, Error>) -> Void) {
        client.request(
            path: "/v1/profile/me",
            method: .get,
            requiresAuth: true
        ) { [decoder] result in
            switch result {
            case .success(let response):
                do {
                    let apiResponse = try decoder.decode(APIResponse<ProfileDataPayload>.self, from: response.data)
                    guard apiResponse.status == "fine" else {
                        let message = apiResponse.detail ?? NSLocalizedString("Не удалось загрузить профиль.", comment: "Profile unexpected status")
                        completion(.failure(ProfileServiceError.unexpectedStatus(message)))
                        return
                    }
                    completion(.success(apiResponse.data))
                } catch {
                    let debugMessage = Self.describeDecodingError(error: error, data: response.data)
                    if AppConfig.DEBUG {
                        print("[ProfileService] decode profile failed: \(debugMessage)")
                    }
                    completion(.failure(ProfileServiceError.decoding(debugDescription: debugMessage)))
                }
            case .failure(let error):
                if case let NetworkError.server(_, data) = error,
                   let data,
                   let message = Self.errorMessage(from: data) {
                    completion(.failure(ProfileServiceError.unexpectedStatus(message)))
                    return
                }
                completion(.failure(error))
            }
        }
    }

    func fetchMyProfile() async throws -> ProfileDataPayload {
        try await withCheckedThrowingContinuation { continuation in
            fetchMyProfile { result in
                continuation.resume(with: result)
            }
        }
    }

    func fetchProfile(userId: UUID, completion: @escaping (Result<ChatProfile, Error>) -> Void) {
        fetchProfile(userId: userId.uuidString, completion: completion)
    }

    func fetchProfile(userId: String, completion: @escaping (Result<ChatProfile, Error>) -> Void) {
        let sanitizedId = userId.trimmingCharacters(in: .whitespacesAndNewlines)

        client.request(
            path: "/v1/profile/\(sanitizedId)",
            method: .get,
            requiresAuth: true
        ) { [decoder] result in
            switch result {
            case .success(let response):
                do {
                    let profile = try Self.decodeProfileResponse(
                        data: response.data,
                        decoder: decoder,
                        requestedId: sanitizedId
                    )
                    completion(.success(profile))
                } catch {
                    if AppConfig.DEBUG {
                        print("[ProfileService] decode profile by id failed: \(error)")
                    }
                    completion(.failure(error))
                }
            case .failure(let error):
                if case let NetworkError.server(_, data) = error,
                   let data,
                   let message = Self.errorMessage(from: data) {
                    completion(.failure(ProfileServiceError.unexpectedStatus(message)))
                    return
                }
                completion(.failure(error))
            }
        }
    }

    func fetchProfile(userId: UUID) async throws -> ChatProfile {
        try await fetchProfile(userId: userId.uuidString)
    }

    func fetchProfile(userId: String) async throws -> ChatProfile {
        try await withCheckedThrowingContinuation { continuation in
            fetchProfile(userId: userId, completion: { result in
                continuation.resume(with: result)
            })
        }
    }

    func updateProfile(_ payload: ProfileUpdateRequestPayload, completion: @escaping (Result<String, Error>) -> Void) {
        let encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase

        guard let body = try? encoder.encode(payload) else {
            let message = NSLocalizedString("Не удалось подготовить данные запроса.", comment: "Profile update encoding error")
            completion(.failure(ProfileServiceError.encoding(message)))
            return
        }
        
        client.request(
            path: "/v1/profile/edit",
            method: .put,
            body: body,
            requiresAuth: true
        ) { result in
            switch result {
            case .success(let response):
                do {
                    let decoder = JSONDecoder()
                    decoder.keyDecodingStrategy = .convertFromSnakeCase
                    let apiResponse = try decoder.decode(APIResponse<MessagePayload>.self, from: response.data)
                    guard apiResponse.status == "fine" else {
                        let message = apiResponse.detail ?? NSLocalizedString("Не удалось сохранить изменения профиля.", comment: "Profile update unexpected status")
                        completion(.failure(ProfileServiceError.unexpectedStatus(message)))
                        return
                    }
                    completion(.success(apiResponse.data.message))
                } catch {
                    let debugMessage = Self.describeDecodingError(error: error, data: response.data)
                    if AppConfig.DEBUG {
                        print("[ProfileService] decode update response failed: \(debugMessage)")
                    }
                    if AppConfig.DEBUG {
                        completion(.failure(ProfileServiceError.decoding(debugDescription: debugMessage)))
                    } else {
                        let message = NSLocalizedString("Не удалось обработать ответ сервера.", comment: "Profile update decode error")
                        completion(.failure(ProfileServiceError.unexpectedStatus(message)))
                    }
                }
            case .failure(let error):
                if case let NetworkError.server(_, data) = error,
                   let data,
                   let message = Self.errorMessage(from: data) {
                    completion(.failure(ProfileServiceError.unexpectedStatus(message)))
                    return
                }
                completion(.failure(error))
            }
        }
    }

    func updateProfile(_ payload: ProfileUpdateRequestPayload) async throws -> String {
        try await withCheckedThrowingContinuation { continuation in
            updateProfile(payload) { result in
                continuation.resume(with: result)
            }
        }
    }

    func uploadAvatar(image: UIImage, completion: @escaping (Result<String, Error>) -> Void) {
        guard let imageData = image.jpegData(compressionQuality: 0.9) else {
            let message = NSLocalizedString("Не удалось подготовить изображение для загрузки.", comment: "Avatar encoding error")
            completion(.failure(ProfileServiceError.encoding(message)))
            return
        }

        let boundary = "Boundary-\(UUID().uuidString)"
        let body = Self.makeMultipartBody(
            data: imageData,
            boundary: boundary,
            fieldName: "file",
            filename: "avatar.jpg",
            mimeType: "image/jpeg"
        )

        client.request(
            path: "/v1/storage/upload/avatar",
            method: .post,
            body: body,
            contentType: "multipart/form-data; boundary=\(boundary)",
            requiresAuth: true
        ) { result in
            switch result {
            case .success(let response):
                do {
                    let decoder = JSONDecoder()
                    decoder.keyDecodingStrategy = .convertFromSnakeCase
                    let apiResponse = try decoder.decode(APIResponse<UploadAvatarPayload>.self, from: response.data)
                    guard apiResponse.status == "fine" else {
                        let message = apiResponse.detail ?? NSLocalizedString("Не удалось обновить аватар.", comment: "Avatar upload unexpected status")
                        completion(.failure(ProfileServiceError.unexpectedStatus(message)))
                        return
                    }
                    completion(.success(apiResponse.data.fileId))
                } catch {
                    let debugMessage = Self.describeDecodingError(error: error, data: response.data)
                    if AppConfig.DEBUG {
                        print("[ProfileService] decode upload avatar failed: \(debugMessage)")
                    }
                    if AppConfig.DEBUG {
                        completion(.failure(ProfileServiceError.decoding(debugDescription: debugMessage)))
                    } else {
                        let message = NSLocalizedString("Не удалось обработать ответ сервера.", comment: "Avatar upload decode error")
                        completion(.failure(ProfileServiceError.unexpectedStatus(message)))
                    }
                }
            case .failure(let error):
                if case let NetworkError.server(_, data) = error,
                   let data,
                   let message = Self.errorMessage(from: data) {
                    completion(.failure(ProfileServiceError.unexpectedStatus(message)))
                    return
                }
                completion(.failure(error))
            }
        }
    }

    func uploadAvatar(image: UIImage) async throws -> String {
        try await withCheckedThrowingContinuation { continuation in
            uploadAvatar(image: image) { result in
                continuation.resume(with: result)
            }
        }
    }

    private static func decodeProfileResponse(
        data: Data,
        decoder: JSONDecoder,
        requestedId: String
    ) throws -> ChatProfile {
        let defaultErrorMessage = NSLocalizedString("Не удалось загрузить профиль.", comment: "Profile unexpected status")
        var dictionaryDecodeError: String?

        do {
            let apiResponse = try decoder.decode(APIResponse<[String: ChatProfile]>.self, from: data)
            guard apiResponse.status == "fine" else {
                let message = apiResponse.detail ?? defaultErrorMessage
                throw ProfileServiceError.unexpectedStatus(message)
            }

            let normalizedKey = requestedId.lowercased()
            if let profile = apiResponse.data[requestedId]
                ?? apiResponse.data[normalizedKey]
                ?? apiResponse.data[requestedId.uppercased()]
                ?? apiResponse.data.first?.value {
                return profile
            }

            throw ProfileServiceError.unexpectedStatus(
                NSLocalizedString("Профиль не найден.", comment: "Profile by id missing")
            )
        } catch let error as ProfileServiceError {
            throw error
        } catch {
            dictionaryDecodeError = Self.describeDecodingError(error: error, data: data)
        }

        do {
            let apiResponse = try decoder.decode(APIResponse<ChatProfile>.self, from: data)
            guard apiResponse.status == "fine" else {
                let message = apiResponse.detail ?? defaultErrorMessage
                throw ProfileServiceError.unexpectedStatus(message)
            }
            return apiResponse.data
        } catch let error as ProfileServiceError {
            throw error
        } catch {
            let singleError = Self.describeDecodingError(error: error, data: data)
            let combined: String
            if let dictionaryDecodeError {
                combined = dictionaryDecodeError + "\nOR\n" + singleError
            } else {
                combined = singleError
            }
            throw ProfileServiceError.decoding(debugDescription: combined)
        }
    }

    private static func decodeDate(from decoder: Decoder) throws -> Date {
        let container = try decoder.singleValueContainer()
        let string = try container.decode(String.self)
        if let date = iso8601WithFractionalSeconds.date(from: string) {
            return date
        }
        if let date = iso8601Simple.date(from: string) {
            return date
        }
        throw DecodingError.dataCorruptedError(
            in: container,
            debugDescription: "Невозможно декодировать дату: \(string)"
        )
    }

    private static func describeDecodingError(error: Error, data: Data) -> String {
        var parts: [String] = []

        if let decodingError = error as? DecodingError {
            parts.append(decodingDescription(from: decodingError))
        } else {
            parts.append(error.localizedDescription)
        }

        if let payload = truncatedPayload(from: data) {
            parts.append("payload=\(payload)")
        }

        return parts.joined(separator: "\n")
    }

    private static func decodingDescription(from error: DecodingError) -> String {
        switch error {
        case .typeMismatch(let type, let context):
            return "Type mismatch for \(type) at \(codingPath(from: context)): \(context.debugDescription)"
        case .valueNotFound(let type, let context):
            return "Value not found for \(type) at \(codingPath(from: context)): \(context.debugDescription)"
        case .keyNotFound(let key, let context):
            return "Missing key '\(key.stringValue)' at \(codingPath(from: context)): \(context.debugDescription)"
        case .dataCorrupted(let context):
            return "Corrupted data at \(codingPath(from: context)): \(context.debugDescription)"
        @unknown default:
            return error.localizedDescription
        }
    }

    private static func codingPath(from context: DecodingError.Context) -> String {
        let path = context.codingPath.map { $0.stringValue }.filter { !$0.isEmpty }
        return path.isEmpty ? "root" : path.joined(separator: ".")
    }

    private static func truncatedPayload(from data: Data, limit: Int = 512) -> String? {
        guard let string = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
              !string.isEmpty else {
            return nil
        }

        if string.count <= limit {
            return string
        }

        let index = string.index(string.startIndex, offsetBy: limit)
        return String(string[string.startIndex..<index]) + "…"
    }

    private static func makeMultipartBody(
        data: Data,
        boundary: String,
        fieldName: String,
        filename: String,
        mimeType: String
    ) -> Data {
        var body = Data()
        let lineBreak = "\r\n"
        if let boundaryData = "--\(boundary)\(lineBreak)".data(using: .utf8) {
            body.append(boundaryData)
        }
        if let dispositionData = "Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(filename)\"\(lineBreak)".data(using: .utf8) {
            body.append(dispositionData)
        }
        if let typeData = "Content-Type: \(mimeType)\(lineBreak)\(lineBreak)".data(using: .utf8) {
            body.append(typeData)
        }
        body.append(data)
        if let closingData = "\(lineBreak)--\(boundary)--\(lineBreak)".data(using: .utf8) {
            body.append(closingData)
        }
        return body
    }

    private static func errorMessage(from data: Data) -> String? {
        if let apiError = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
            if let detail = apiError.detail, !detail.isEmpty {
                return detail
            }
            if let message = apiError.data?.message, !message.isEmpty {
                return message
            }
        }
        if let string = String(data: data, encoding: .utf8), !string.isEmpty {
            return string
        }
        return nil
    }

    private static let iso8601WithFractionalSeconds: ISO8601DateFormatter = {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        return formatter
    }()

    private static let iso8601Simple: ISO8601DateFormatter = {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime]
        return formatter
    }()
}
