//
//  AuthService.swift
//  VolnahubApp
//
//  Created by cheykrym on 09/06/2025.
//

import Foundation

final class AuthService {

    func autoLogin(completion: @escaping (Bool, String?) -> Void) {
        if let currentUser = UserDefaults.standard.string(forKey: "currentUser"),
           let _ = KeychainService.shared.get(forKey: "access_token", service: currentUser),
           let _ = KeychainService.shared.get(forKey: "refresh_token", service: currentUser) {
            if AppConfig.DEBUG { print("AutoLogin: найден текущий пользователь — \(currentUser)") }
            completion(true, nil)
            return
        }

        if AppConfig.DEBUG { print("AutoLogin: текущий пользователь не найден или токены отсутствуют. Пробуем найти другого пользователя...") }

        let allUsers = KeychainService.shared.getAllServices()
        for user in allUsers {
            let hasAccessToken = KeychainService.shared.get(forKey: "access_token", service: user) != nil
            let hasRefreshToken = KeychainService.shared.get(forKey: "refresh_token", service: user) != nil

            if hasAccessToken && hasRefreshToken {
                UserDefaults.standard.set(user, forKey: "currentUser")
                if AppConfig.DEBUG { print("AutoLogin: переключились на пользователя \(user)") }
                completion(true, nil)
                return
            }
        }

        completion(false, nil)
    }

    func login(username: String, password: String, completion: @escaping (Bool, String?) -> Void) {
        let payload = LoginRequest(login: username, password: password)
        guard let body = try? JSONEncoder().encode(payload) else {
            completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: ""))
            return
        }

        NetworkClient.shared.request(
            path: "/v1/auth/login/password",
            method: .post,
            headers: ["X-Client-Type": "ios"],
            body: body,
            requiresAuth: false
        ) { result in
            switch result {
            case .success(let response):
                do {
                    let decoder = JSONDecoder()
                    let apiResponse = try decoder.decode(APIResponse<TokenPairPayload>.self, from: response.data)

                    guard apiResponse.status == "fine" else {
                        let message = apiResponse.detail ?? NSLocalizedString("Неизвестная ошибка", comment: "")
                        completion(false, message)
                        return
                    }

                    let tokens = apiResponse.data
                    KeychainService.shared.save(tokens.access_token, forKey: "access_token", service: username)
                    KeychainService.shared.save(tokens.refresh_token, forKey: "refresh_token", service: username)
                    if let userId = tokens.user_id {
                        KeychainService.shared.save(userId, forKey: "userId", service: username)
                    }
                    UserDefaults.standard.set(username, forKey: "currentUser")

                    NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)

                    completion(true, nil)
                } catch {
                    completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
                }

            case .failure(let error):
                let message = self.loginErrorMessage(for: error)
                completion(false, message)
            }
        }
    }

    func requestLoginCode(identifier: String, completion: @escaping (Bool, String?) -> Void) {
        let payload = LoginCodeRequestPayload(login: identifier)

        guard let body = try? JSONEncoder().encode(payload) else {
            completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: ""))
            return
        }

        NetworkClient.shared.request(
            path: "/v1/auth/login/code",
            method: .post,
            headers: ["X-Client-Type": "ios"],
            body: body,
            requiresAuth: false
        ) { [weak self] result in
            guard let self else { return }

            switch result {
            case .success(let response):
                do {
                    let decoder = JSONDecoder()
                    let apiResponse = try decoder.decode(APIResponse<MessagePayload>.self, from: response.data)
                    guard apiResponse.status == "fine" else {
                        let message = apiResponse.detail ?? apiResponse.data.message
                        completion(false, message)
                        return
                    }
                    completion(true, nil)
                } catch {
                    completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
                }
            case .failure(let error):
                completion(false, self.passwordlessRequestErrorMessage(for: error))
            }
        }
    }

    func loginWithCode(identifier: String, code: String, completion: @escaping (Bool, String?) -> Void) {
        let payload = VerifyCodeRequestPayload(login: identifier, otp: code)

        guard let body = try? JSONEncoder().encode(payload) else {
            completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: ""))
            return
        }

        NetworkClient.shared.request(
            path: "/v1/auth/login/verify_code",
            method: .post,
            headers: ["X-Client-Type": "ios"],
            body: body,
            requiresAuth: false
        ) { [weak self] result in
            guard let self else { return }

            switch result {
            case .success(let response):
                do {
                    let decoder = JSONDecoder()
                    let apiResponse = try decoder.decode(APIResponse<TokenPairPayload>.self, from: response.data)
                    guard apiResponse.status == "fine" else {
                        let message = apiResponse.detail ?? NSLocalizedString("Проверьте код и попробуйте снова.", comment: "")
                        completion(false, message)
                        return
                    }

                    let tokens = apiResponse.data
                    KeychainService.shared.save(tokens.access_token, forKey: "access_token", service: identifier)
                    KeychainService.shared.save(tokens.refresh_token, forKey: "refresh_token", service: identifier)
                    if let userId = tokens.user_id {
                        KeychainService.shared.save(userId, forKey: "userId", service: identifier)
                    }
                    UserDefaults.standard.set(identifier, forKey: "currentUser")

                    NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)

                    completion(true, nil)
                } catch {
                    completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
                }
            case .failure(let error):
                completion(false, self.passwordlessVerifyErrorMessage(for: error))
            }
        }
    }

    func register(username: String, password: String, invite: String?, completion: @escaping (Bool, String?) -> Void) {
        let payload = RegisterRequest(login: username, password: password, invite: invite)
        guard let body = try? JSONEncoder().encode(payload) else {
            completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: ""))
            return
        }

        NetworkClient.shared.request(
            path: "/v1/auth/register",
            method: .post,
            body: body,
            requiresAuth: false
        ) { result in
            switch result {
            case .success(let response):
                do {
                    let decoder = JSONDecoder()
                    let apiResponse = try decoder.decode(APIResponse<RegisterPayload>.self, from: response.data)

                    guard apiResponse.status == "fine" else {
                        let message = apiResponse.detail ?? NSLocalizedString("Неизвестная ошибка", comment: "")
                        completion(false, message)
                        return
                    }

                    if AppConfig.DEBUG { print("Регистрация успешна. Пытаемся сразу войти...") }

                    self.login(username: username, password: password) { loginSuccess, loginMessage in
                        if loginSuccess {
                            completion(true, NSLocalizedString("Регистрация и вход выполнены успешно.", comment: ""))
                        } else {
                            completion(false, loginMessage ?? NSLocalizedString("Регистрация выполнена, но вход не удался.", comment: ""))
                        }
                    }
                } catch {
                    completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
                }

            case .failure(let error):
                let message = self.registerErrorMessage(for: error)
                completion(false, message)
            }
        }
    }

    func changePassword(oldPassword: String, newPassword: String, completion: @escaping (Bool, String?) -> Void) {
        let payload = ChangePasswordRequestPayload(old_password: oldPassword, new_password: newPassword)
        guard let body = try? JSONEncoder().encode(payload) else {
            completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: ""))
            return
        }

        NetworkClient.shared.request(
            path: "/v1/auth/password/change",
            method: .post,
            body: body,
            requiresAuth: true
        ) { result in
            switch result {
            case .success(let response):
                do {
                    let decoder = JSONDecoder()
                    let apiResponse = try decoder.decode(APIResponse<MessagePayload>.self, from: response.data)

                    guard apiResponse.status == "fine" else {
                        let message = apiResponse.detail ?? NSLocalizedString("Не удалось обновить пароль.", comment: "")
                        completion(false, message)
                        return
                    }

                    completion(true, NSLocalizedString(apiResponse.data.message, comment: ""))
                } catch {
                    completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
                }

            case .failure(let error):
                let message = self.changePasswordErrorMessage(for: error)
                completion(false, message)
            }
        }
    }

    func logoutCurrentUser(completion: @escaping (Bool, String?) -> Void) {
        guard let currentUser = UserDefaults.standard.string(forKey: "currentUser") else {
            completion(false, "Не найден текущий пользователь.")
            return
        }

        KeychainService.shared.delete(forKey: "access_token", service: currentUser)
        KeychainService.shared.delete(forKey: "refresh_token", service: currentUser)
        KeychainService.shared.delete(forKey: "userId", service: currentUser)

        UserDefaults.standard.removeObject(forKey: "currentUser")

        NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)

        let allUsers = KeychainService.shared.getAllServices()
        for user in allUsers {
            let hasAccessToken = KeychainService.shared.get(forKey: "access_token", service: user) != nil
            let hasRefreshToken = KeychainService.shared.get(forKey: "refresh_token", service: user) != nil

            if hasAccessToken && hasRefreshToken {
                UserDefaults.standard.set(user, forKey: "currentUser")
                if AppConfig.DEBUG { print("Logout: переключились на пользователя \(user)") }
                NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
                completion(true, nil)
                return
            }
        }

        completion(false, nil)
    }

    private func loginErrorMessage(for error: NetworkError) -> String {
        switch error {
        case .network(let err):
            return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription)
        case .server(let statusCode, _):
            switch statusCode {
            case 401:
                return NSLocalizedString("Неверный логин или пароль.", comment: "")
            case 429:
                return NSLocalizedString("Слишком много запросов.", comment: "")
            case 502:
                return NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")
            default:
                return String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(statusCode)")
            }
        case .unauthorized:
            return NSLocalizedString("Неверный логин или пароль.", comment: "")
        case .invalidURL, .noResponse:
            return NSLocalizedString("Некорректный ответ от сервера.", comment: "")
        }
    }

    private func registerErrorMessage(for error: NetworkError) -> String {
        switch error {
        case .network(let err):
            return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription)
        case .server(let statusCode, let data):
            if let data,
               let response = try? JSONDecoder().decode(ErrorResponse.self, from: data),
               let message = response.data?.message {
                return mappedRegistrationMessage(for: message, statusCode: statusCode)
            }

            let message = extractMessage(from: data)
            
            switch statusCode {
            case 400:
                return NSLocalizedString("Неверный запрос (400).", comment: "")
            case 403:
                return NSLocalizedString("Регистрация запрещена.", comment: "")
            case 409:
                return NSLocalizedString("Логин уже занят.", comment: "")
            case 422:
                if let message {
                    if message == "Value error, Login must not end with 'bot' for non-bot accounts"{
                        return NSLocalizedString("Login must not end with 'bot' for non-bot accounts", comment: "")
                    }
                    return message
                } else {
                    return NSLocalizedString("Ошибка в данных. Проверьте введённую информацию.", comment: "")
                }
            case 429:
                return NSLocalizedString("Слишком много запросов.", comment: "")
            case 502:
                return NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")
            default:
                return String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(statusCode)")
            }
        case .unauthorized:
            return NSLocalizedString("Регистрация запрещена.", comment: "")
        case .invalidURL, .noResponse:
            return NSLocalizedString("Некорректный ответ от сервера.", comment: "")
        }
    }

    private func passwordlessRequestErrorMessage(for error: NetworkError) -> String {
        switch error {
        case .network(let err):
            return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription)
        case .server(let statusCode, let data):
            let message = extractMessage(from: data)

            switch statusCode {
            case 401, 404:
                return message ?? NSLocalizedString("Аккаунт не найден.", comment: "")
            case 403:
                return message ?? NSLocalizedString("Этому аккаунту недоступен вход по коду.", comment: "")
            case 422:
                return message ?? NSLocalizedString("Неверный логин. Проверьте и попробуйте снова.", comment: "")
            case 429:
                return NSLocalizedString("Слишком много попыток. Попробуйте позже.", comment: "")
            case 502:
                return NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")
            default:
                if let message {
                    return message
                }
                return String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(statusCode)")
            }
        case .unauthorized:
            return NSLocalizedString("Необходимо авторизоваться заново.", comment: "")
        case .invalidURL, .noResponse:
            return NSLocalizedString("Некорректный ответ от сервера.", comment: "")
        }
    }

    private func passwordlessVerifyErrorMessage(for error: NetworkError) -> String {
        switch error {
        case .network(let err):
            return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription)
        case .server(let statusCode, let data):
            let message = extractMessage(from: data)

            switch statusCode {
            case 401:
                return message ?? NSLocalizedString("Неверный или просроченный код.", comment: "")
            case 403:
                return message ?? NSLocalizedString("Этот аккаунт недоступен.", comment: "")
            case 404:
                return message ?? NSLocalizedString("Аккаунт не найден.", comment: "")
            case 422:
                return message ?? NSLocalizedString("Некорректные данные. Проверьте код и логин.", comment: "")
            case 429:
                return NSLocalizedString("Слишком много попыток. Попробуйте позже.", comment: "")
            case 502:
                return NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")
            default:
                if let message {
                    return message
                }
                return String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(statusCode)")
            }
        case .unauthorized:
            return NSLocalizedString("Сессия недействительна. Авторизуйтесь заново.", comment: "")
        case .invalidURL, .noResponse:
            return NSLocalizedString("Некорректный ответ от сервера.", comment: "")
        }
    }

    private func mappedRegistrationMessage(for message: String, statusCode: Int) -> String {
        if statusCode == 400 {
            if message.contains("Invalid invitation code") {
                return NSLocalizedString("Неверный код приглашения.", comment: "")
            } else if message.contains("This invitation is not active") {
                return NSLocalizedString("Приглашение не активно.", comment: "")
            } else if message.contains("This invitation has reached its usage limit") {
                return NSLocalizedString("Приглашение достигло лимита использования.", comment: "")
            } else if message.contains("This invitation has expired") {
                return NSLocalizedString("Приглашение истекло.", comment: "")
            } else if message.contains("Login already registered") {
                return NSLocalizedString("Логин уже занят.", comment: "")
            }
        }

        if statusCode == 403 {
            if message.contains("Registration is currently disabled") {
                return NSLocalizedString("Регистрация временно недоступна.", comment: "")
            }
        }
        
        if statusCode == 429 {
            return NSLocalizedString("Слишком много запросов.", comment: "")
        }

        if statusCode == 502 {
            return NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")
        }

        return message
    }

    private func changePasswordErrorMessage(for error: NetworkError) -> String {
        switch error {
        case .network(let err):
            return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription)
        case .server(let statusCode, let data):
            let message = extractMessage(from: data)

            switch statusCode {
            case 401:
                return NSLocalizedString("Необходимо авторизоваться заново.", comment: "")
            case 403:
                if let message,
                   Self.changePasswordForbiddenMessages.contains(message) {
                    return NSLocalizedString(message, comment: "")
                }
                return NSLocalizedString("Старый пароль указан неверно или совпадает с новым.", comment: "")
            case 422:
                if let message {
                    return message
                }
                return NSLocalizedString("Проверьте данные и повторите попытку.", comment: "")
            case 429:
                return NSLocalizedString("Слишком много попыток. Попробуйте позже.", comment: "")
            default:
                if let message {
                    return message
                }
                return String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(statusCode)")
            }
        case .unauthorized:
            return NSLocalizedString("Необходимо авторизоваться заново.", comment: "")
        case .invalidURL, .noResponse:
            return NSLocalizedString("Некорректный ответ от сервера.", comment: "")
        }
    }

    private func extractMessage(from data: Data?) -> String? {
        guard let data else { return nil }
        let decoder = JSONDecoder()
        if let response = try? decoder.decode(ErrorResponse.self, from: data) {
            if let message = response.data?.message, !message.isEmpty {
                return message
            }
            if let detail = response.detail, !detail.isEmpty {
                return detail
            }
        }

        if let jsonObject = try? JSONSerialization.jsonObject(with: data) {
            if let dictionary = jsonObject as? [String: Any] {
                if let detail = Self.normalizedMessage(dictionary["detail"] as? String) {
                    return detail
                }
                if let dataDict = dictionary["data"] as? [String: Any],
                   let message = Self.normalizedMessage(dataDict["message"] as? String) {
                    return message
                }
                if let errors = dictionary["errors"] as? [[String: Any]],
                   let firstMessage = errors.compactMap({ Self.normalizedMessage($0["message"] as? String) }).first {
                    return firstMessage
                }
            } else if let array = jsonObject as? [[String: Any]] {
                if let firstMessage = array.compactMap({ item -> String? in
                    if let detail = Self.normalizedMessage(item["detail"] as? String) {
                        return detail
                    }
                    if let message = Self.normalizedMessage(item["message"] as? String) {
                        return message
                    }
                    if let msg = Self.normalizedMessage(item["msg"] as? String) {
                        return msg
                    }
                    return nil
                }).first {
                    return firstMessage
                }
            }
        }

        if let string = Self.normalizedMessage(String(data: data, encoding: .utf8)) {
            return string
        }

        return nil
    }
}

private extension AuthService {
    static let changePasswordForbiddenMessages: Set<String> = [
        "Неверный текущий пароль",
        "Пароль должен отличаться от старого",
        "Пароль не удовлетворяет требованиям"
    ]

    static func normalizedMessage(_ raw: String?) -> String? {
        guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else {
            return nil
        }
        return raw
    }
}

private struct LoginRequest: Encodable {
    let login: String
    let password: String
}

private struct LoginCodeRequestPayload: Encodable {
    let login: String
}

private struct VerifyCodeRequestPayload: Encodable {
    let login: String
    let otp: String
}

private struct RegisterRequest: Encodable {
    let login: String
    let password: String
    let invite: String?
}

private struct ChangePasswordRequestPayload: Encodable {
    let old_password: String
    let new_password: String
}
