import Foundation

private struct AuthPayload: Decodable {
    let user: UserAccount
    let token: String
    let refreshToken: String
    let expiresAt: Date
}

private struct LoginBody: Encodable {
    let identifier: String
    let password: String
}

private struct RegisterBody: Encodable {
    let username: String
    let identifier: String
    let password: String
}

private struct UpdateProfileBody: Encodable {
    let username: String?
    let email: String?
    let phoneNumber: String?
    let avatarURL: String?
}

private struct ChangePasswordBody: Encodable {
    let oldPassword: String
    let newPassword: String
}

final class RemoteAuthService: AuthServiceProtocol {
    private let client: APIClient
    private let profileKey = "com.celestia.trace.cached_server_profile"
    private var cachedUser: UserAccount?

    init(client: APIClient = .shared) {
        self.client = client
        // Remove credentials created by the retired local-only auth repository.
        // These keys never contained a valid server session.
        UserDefaults.standard.removeObject(forKey: "com.celestia.trace.registered_users_db")
        UserDefaults.standard.removeObject(forKey: "com.celestia.trace.current_user_session")
        if let data = UserDefaults.standard.data(forKey: profileKey) {
            cachedUser = try? JSONDecoder().decode(UserAccount.self, from: data)
        }
    }

    func restoreSession(completion: @escaping (AuthResult<UserAccount?>) -> Void) {
        Task {
            guard await client.hasStoredSession() else {
                clearCachedProfile()
                completion(.success(nil))
                return
            }
            do {
                let user: UserAccount = try await client.request("user/profile", authenticated: true)
                cache(user)
                completion(.success(user))
            } catch APIError.transport {
                completion(.success(cachedUser))
            } catch {
                await client.clearSession()
                clearCachedProfile()
                completion(.failure(map(error)))
            }
        }
    }

    func login(request: LoginRequest, completion: @escaping (AuthResult<UserAccount>) -> Void) {
        performAuthentication(path: "auth/login", body: LoginBody(identifier: request.identifier, password: request.password), completion: completion)
    }

    func register(request: RegisterRequest, completion: @escaping (AuthResult<UserAccount>) -> Void) {
        performAuthentication(
            path: "auth/register",
            body: RegisterBody(username: request.username, identifier: request.identifier, password: request.password),
            completion: completion
        )
    }

    func updateProfile(request: UpdateProfileRequest, completion: @escaping (AuthResult<UserAccount>) -> Void) {
        Task {
            do {
                let body = UpdateProfileBody(username: request.username, email: request.email, phoneNumber: request.phoneNumber, avatarURL: request.avatarURL)
                let user: UserAccount = try await client.request("user/profile", method: .put, body: try JSONEncoder().encode(body), authenticated: true)
                cache(user)
                completion(.success(user))
            } catch {
                completion(.failure(map(error)))
            }
        }
    }

    func changePassword(request: ChangePasswordRequest, completion: @escaping (AuthResult<Bool>) -> Void) {
        Task {
            do {
                let body = ChangePasswordBody(oldPassword: request.oldPassword, newPassword: request.newPassword)
                try await client.requestVoid("user/change-password", method: .post, body: try JSONEncoder().encode(body), authenticated: true)
                completion(.success(true))
            } catch {
                completion(.failure(map(error)))
            }
        }
    }

    func logout(completion: @escaping () -> Void) {
        Task {
            try? await client.requestVoid("auth/logout", method: .post, authenticated: true)
            await client.clearSession()
            clearCachedProfile()
            completion()
        }
    }

    func getCurrentUser() -> UserAccount? {
        cachedUser
    }

    private func performAuthentication<Body: Encodable>(
        path: String,
        body: Body,
        completion: @escaping (AuthResult<UserAccount>) -> Void
    ) {
        Task {
            do {
                let payload: AuthPayload = try await client.request(path, method: .post, body: try JSONEncoder().encode(body))
                try await client.storeTokens(accessToken: payload.token, refreshToken: payload.refreshToken, expiresAt: payload.expiresAt)
                cache(payload.user)
                completion(.success(payload.user))
            } catch {
                completion(.failure(map(error)))
            }
        }
    }

    private func cache(_ user: UserAccount) {
        cachedUser = user
        if let data = try? JSONEncoder().encode(user) {
            UserDefaults.standard.set(data, forKey: profileKey)
        }
    }

    private func clearCachedProfile() {
        cachedUser = nil
        UserDefaults.standard.removeObject(forKey: profileKey)
    }

    private func map(_ error: Error) -> AuthError {
        if case APIError.unauthorized = error { return .invalidCredentials }
        if case APIError.server(let code, _) = error {
            if code == 409 { return .userAlreadyExists }
            if code == 401 { return .invalidCredentials }
        }
        return .networkError(error.localizedDescription)
    }
}
